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

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

Download ZIP

declared tools #31

Merged@k33g wants to merge feature/declared-tools into main
Merged as 23419402393a.
40 files changed
modified .tickets/issues/0031-how-to-use-mini-me-with-gemini.yaml +3 -2
@@ -6,8 +6,9 @@ author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-09-21T04:01:15.166Z
9-updatedAt: 2026-10-06T04:01:18.848Z
10-labels: []
9+updatedAt: 2026-10-16T11:37:39.982Z
10+labels:
11+ - wontfix
1112 epic: Providers (LLM Engine)
1213 body: |
1314 ## Analysis (Claude, 2026-09-21 — not implemented)
@@ -6,8 +6,9 @@ author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-09-21T04:01:15.166Z8 createdAt: 2026-09-21T04:01:15.166Z
9-updatedAt: 2026-10-06T04:01:18.848Z9+updatedAt: 2026-10-16T11:37:39.982Z
10-labels: []10+labels:
11+ - wontfix
11 epic: Providers (LLM Engine)12 epic: Providers (LLM Engine)
12 body: |13 body: |
13 ## Analysis (Claude, 2026-09-21 — not implemented)14 ## Analysis (Claude, 2026-09-21 — not implemented)
modified .tickets/issues/0048-add-a-builtin-tool-to-search-into-the-mm-session.yaml +2 -2
@@ -6,9 +6,9 @@ author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-09-27T03:45:48.121Z
9-updatedAt: 2026-10-12T09:53:49.053Z
9+updatedAt: 2026-10-16T11:48:27.806Z
1010 labels: []
11-milestone: v0.5.0
11+milestone: v0.6.0
1212 epic: Agent Memory
1313 body: ""
1414 tasks: []
@@ -6,9 +6,9 @@ author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-09-27T03:45:48.121Z8 createdAt: 2026-09-27T03:45:48.121Z
9-updatedAt: 2026-10-12T09:53:49.053Z9+updatedAt: 2026-10-16T11:48:27.806Z
10 labels: []10 labels: []
11-milestone: v0.5.011+milestone: v0.6.0
12 epic: Agent Memory12 epic: Agent Memory
13 body: ""13 body: ""
14 tasks: []14 tasks: []
modified .tickets/issues/0049-recreate-the-readme.yaml +2 -2
@@ -6,9 +6,9 @@ author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-09-28T18:44:44.821Z
9-updatedAt: 2026-10-12T09:54:01.192Z
9+updatedAt: 2026-10-16T11:48:41.005Z
1010 labels: []
11-milestone: v0.5.0
11+milestone: v0.6.0
1212 epic: ToDo
1313 body: ""
1414 tasks: []
@@ -6,9 +6,9 @@ author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-09-28T18:44:44.821Z8 createdAt: 2026-09-28T18:44:44.821Z
9-updatedAt: 2026-10-12T09:54:01.192Z9+updatedAt: 2026-10-16T11:48:41.005Z
10 labels: []10 labels: []
11-milestone: v0.5.011+milestone: v0.6.0
12 epic: ToDo12 epic: ToDo
13 body: ""13 body: ""
14 tasks: []14 tasks: []
modified .tickets/issues/0055-how-to-query-another-model-and-force-the-json-ou.yaml +3 -2
@@ -1,13 +1,14 @@
11 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
22 id: 55
33 title: How to query another model and force the JSON output (then use the result)
4-state: open
4+state: closed
55 author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-09-30T06:37:48.693Z
9-updatedAt: 2026-10-06T13:14:33.928Z
9+updatedAt: 2026-10-16T11:36:43.967Z
1010 labels: []
11+milestone: v0.3.0
1112 epic: Models
1213 related:
1314 - 54
@@ -1,13 +1,14 @@
1 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.1 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2 id: 552 id: 55
3 title: How to query another model and force the JSON output (then use the result)3 title: How to query another model and force the JSON output (then use the result)
4-state: open4+state: closed
5 author:5 author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-09-30T06:37:48.693Z8 createdAt: 2026-09-30T06:37:48.693Z
9-updatedAt: 2026-10-06T13:14:33.928Z9+updatedAt: 2026-10-16T11:36:43.967Z
10 labels: []10 labels: []
11+milestone: v0.3.0
11 epic: Models12 epic: Models
12 related:13 related:
13 - 5414 - 54
modified .tickets/issues/0087-hook-for-ask-agents.yaml +2 -2
@@ -1,12 +1,12 @@
11 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
22 id: 87
33 title: Hook for ask agents
4-state: open
4+state: closed
55 author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-10-15T21:55:43.205Z
9-updatedAt: 2026-10-15T23:50:00.000Z
9+updatedAt: 2026-10-16T11:35:47.159Z
1010 labels: []
1111 milestone: v0.5.0
1212 body: |
@@ -1,12 +1,12 @@
1 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.1 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2 id: 872 id: 87
3 title: Hook for ask agents3 title: Hook for ask agents
4-state: open4+state: closed
5 author:5 author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-10-15T21:55:43.205Z8 createdAt: 2026-10-15T21:55:43.205Z
9-updatedAt: 2026-10-15T23:50:00.000Z9+updatedAt: 2026-10-16T11:35:47.159Z
10 labels: []10 labels: []
11 milestone: v0.5.011 milestone: v0.5.0
12 body: |12 body: |
added .tickets/issues/0088-custom-tools-declared-in-the-agent-file-command.yaml +143 -0
new file mode 100644
@@ -0,0 +1,143 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 88
3+title: Custom tools declared in the agent file (command tools)
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-16T12:00:00.000Z
9+updatedAt: 2026-10-16T17:00:00.000Z
10+labels:
11+ - kind::feature
12+ - builtin tool
13+milestone: v0.6.0
14+epic: Builtin tools
15+body: |
16+ ## Question
17+ Skills can already run commands (`read_skill`, then `bash`). Is it worth letting an agent author declare **typed tools** in the agent's YAML, or are skills enough?
18+
19+ ## Why skills may not be enough (observed in this project)
20+ - **Routing comes first.** The model has to pick the right skill before anything runs: `where is the Greet function defined?` routes to `greetings`, and `bg-jobs` misses in English on both models (eval, 2026-09-21).
21+ - **Execution depends on the model.** With a ` ```bash ` fence, mellum2-12b reproduced the command instead of running it (3/3); qwen3.5-4b ran it (2026-09-21).
22+ - **Two calls, the command rewritten by the model.** `read_skill`, then `bash` with a command line the model composes — each step can fail on a 4–12B model.
23+ - **Consent is all or nothing.** Everything goes through `bash`, so "always allow this script" means "always allow bash".
24+
25+ ## What already exists
26+ MCP tools, declared with the server's input schema since 2026-10-12 — but **over HTTP only**: the stdio transport is not implemented (`internal/mcp/client.go`: "stdio transport for MCP is not yet implemented"). So a custom tool today is an HTTP server to write, start and keep running beside mm — heavy for "run this script with two arguments".
27+
28+ ## Proposal (to be confirmed by the measurement below)
29+ A `tools:` block in the agent file. Each entry is one command, its arguments passed as `argv` (no shell, so no injection through a parameter):
30+ ```yaml
31+ tools:
32+ - name: run_tests
33+ description: Run the Go tests of a package
34+ parameters:
35+ package: { type: string, description: "e.g. ./internal/lsp" }
36+ command: ["go", "test", "{{package}}", "-count=1"]
37+ ```
38+ Skills stay for procedures and know-how; declared tools for frequent, precise actions.
39+
40+ ## Measure BEFORE building
41+ The cheapest honest comparison needs no mm code: an MCP HTTP stand-in exposes the **same** tool (same description, schema) as the proposed `tools:` entry, so the model sees nearly the declaration the feature would produce. One difference: MCP tool names are prefixed with the server name (`<server>_<tool>`).
42+ - **Conditions**: (A) skill + `bash` (current way, skill written by the rules in `reference/skill-format.md`: no fence, "call your `bash` tool"); (B) the same action as a declared tool (MCP stand-in), skill removed.
43+ - **Cases**: 3–4 actions with arguments (e.g. run the tests of a package, a script with two parameters, one action whose skill name collides with a common word), each phrased 2 ways, one in French.
44+ - **Models**: mellum2-12b (DMR) and one ~4B (qwen3.5-4b or Gemma 4 E4B). 5 runs per case and condition, `mm -p`, `yolo: true`.
45+ - **Measured**: the command really ran with the right arguments (side effect checked by a file the script writes, not by the model's words); number of tool calls; wall time.
46+ - **Decision rule**: build it if (B) beats (A) clearly on at least one model (e.g. ≥ 2 more successes out of 5 on several cases); otherwise close as "skills are enough", with the numbers.
47+
48+ ## Open questions for the user (if built)
49+ 1. Parameter types: strings only, or string/number/boolean/enum?
50+ 2. Consent: one rule per declared tool (`consent.Policy` already remembers per tool) — default "ask" even with `yolo: true`, or follow `yolo`?
51+ 3. Output: same truncation and timeout as `bash` (30 s budget), or per-tool `timeout`?
52+ 4. Working directory: the project root, like `bash`?
53+ 5. Relation to ticket 46 (tools as Wasm plugins): this is the plain-command version; keep 46 for sandboxed tools.
54+ ## Measured (2026-10-16)
55+ Setup: DMR, `mm -p`, `yolo: true`, `bashTool: true`, edit/plan/fetch tools off, `maxTurns: 6`, default system prompt. Three scripts that log their argv (`release <version> <channel>`, `pkgtest <package>`, `status <service>`). (A) three skills in the `reference/skill-format.md` style (no fence, "call your `bash` tool"); (B) no skills, an MCP HTTP stand-in (Python) exposing the same three actions as typed tools, `permissions: auto`. Success = the script's first logged call has exactly the expected arguments. 5 runs per cell.
56+
57+ | model | case | A skill + bash | B typed tool |
58+ | --- | --- | --- | --- |
59+ | mellum2-12b | release EN / FR | 5/5 · 5/5 | 5/5 · **0/5** |
60+ | mellum2-12b | pkgtest EN / FR | 5/5 · **0/5** | 5/5 · 5/5 |
61+ | mellum2-12b | status EN / FR | **0/5 · 0/5** | 5/5 · 5/5 |
62+ | qwen3.5-4b | all six | 30/30 | 30/30 |
63+
64+ Totals: mellum2 A 15/30 (avg 4.5 s), B 25/30 (2.8 s); qwen3.5-4b A 30/30 (12.7 s), B 30/30 (7.7 s). B always takes one tool call, A two (`read_skill` + `bash`) when it works.
65+
66+ What the failures are:
67+ - **A, mellum2, status (10/10 failed)**: the skill is never read; the model improvises `curl localhost:8080/health`, `ps aux | grep billing`… and answers "the billing service seems to be running" — a wrong answer, not just a miss. The routing failure this ticket is about.
68+ - **A, mellum2, pkgtest FR**: no skill read; the model explores (`ls`, `cat scripts/pkgtest.sh`) and runs `./pkgtest.sh greet` instead of `./greet`.
69+ - **B, mellum2, release FR (5/5 failed)**: the model packs every argument into ONE field — `{version: "1.4.2 1.4.2, channel beta"}`, `channel` absent — the same mellum2 quirk as on `lsp` rename (2026-09-18). `validateRequiredParams` refuses the call before it reaches the server, the model repeats it, the loop detector stops the turn after 6. In `-p` mode the refusal text is not shown at all.
70+ - qwen3.5-4b: no failure in either condition; B is ~40% faster (one model round-trip less).
71+
72+ Reading: by the decision rule, **B wins** (mellum2: +5/5 on three cells, −5/5 on one; qwen: same success, faster). But typed tools bring their own failure mode on mellum2 — argument packing — that a command line in a skill does not have. If built, a declared tool needs (1) a refusal that tells the model what it sent and what is missing, and (2) maybe the `tools.normalize`-style recovery that `lsp` has.
73+
74+ Caveats: one fixture, 3 actions, 2 phrasings, N=5, two models; the B tool names carry the `proj_` MCP prefix; the bench lived in the session scratchpad (not committed).
75+
76+ Found on the way (separate from this ticket): with `yolo: true` and an MCP server left at the default `permissions: ask`, `-p` prints "[yolo is off and this mode cannot ask…]" — misleading, yolo IS on — and the model then loops on the refused call until the loop detector stops it.
77+ ## Decisions (user, 2026-10-16)
78+ 1. Build it (option B, typed tools).
79+ 2. All parameter types: string, number, integer, boolean, plus `enum` on any of them.
80+ 3. Consent follows `yolo`: no prompt when yolo is on.
81+ Defaults taken for the remaining open questions (not overruled): working directory = bash's; timeout = `bashTimeout`, per-tool `timeout`; output = bash's truncation and echo; ticket 46 (Wasm) kept separate.
82+
83+ ## Implemented (2026-10-16, branch `feature/declared-tools`)
84+ - `internal/config/tools.go`: `DeclaredTool`, `ToolParameter`, `ToolParameters` (keeps file order via `UnmarshalYAML`), `BuiltinToolNames` (now also used by `mcp.BuiltinTools`, which lacked `fetch_content` and `ask_agent`), `Coerce`/`Allows`, `Placeholders`/`ReplacePlaceholders`, load-time validation.
85+ - `internal/tools/declared.go`: `DeclaredTool(g, d, t)`; schema without `required`/`enum` keywords (as `mcp.modelSchema`); `buildArgv` with an invalid-call refusal that quotes the call back; terminal (`🧰`) and evented (`execute`) paths; `consent.Gate` / `confirmed`.
86+ - `internal/tools/tools.go`: `runArgv` extracted from `runBash` (shared by both); a program that cannot be started is reported.
87+ - `main.go`: `assembleDeclared`, after MCP; a name taken by an MCP tool is skipped with a warning.
88+ - Not done: declared tools are not counted in the turn recap (`⚙ N command(s)` counts bash only) — would touch the three front ends.
89+
90+ Measured once built (same bench, condition D = the real feature): **mellum2-12b 30/30 (1.3 s avg), qwen3.5-4b 30/30 (5.8 s)**, every run in ONE call, no invalid call. The French release request that failed 5/5 through the MCP stand-in succeeded 5/5; which difference matters (no `proj_` prefix, no "MCP tool from server" text, the "Give each parameter as its own field." line) was not isolated.
91+ Demo: `demos/declared-tools/` (ops desk, four declared tools, every parameter type, mellum2 on DMR): 24/24 action requests in one call. Found there: an `enum` does not stop the model from silently swapping an unknown value for an allowed one ("alpha" → `beta`, 3/3) — fixed by the demo's system prompt.
92+
93+tasks:
94+ - id: 1
95+ title: "Measure: MCP stand-in vs skill + bash, 2 models, 5 runs per case"
96+ state: closed
97+ priority: high
98+ author:
99+ name: k33g
100+ email: ph.charriere@gmail.com
101+ createdAt: 2026-10-16T12:00:00.000Z
102+ updatedAt: 2026-10-16T14:30:00.000Z
103+ closedAt: 2026-10-16T14:30:00.000Z
104+ - id: 2
105+ title: "Decide with the user: build or close, answer the open questions"
106+ state: closed
107+ priority: high
108+ author:
109+ name: k33g
110+ email: ph.charriere@gmail.com
111+ createdAt: 2026-10-16T12:00:00.000Z
112+ updatedAt: 2026-10-16T17:00:00.000Z
113+ closedAt: 2026-10-16T17:00:00.000Z
114+ - id: 3
115+ title: "Implement declared tools (config, tools, main) with tests"
116+ state: closed
117+ priority: high
118+ author:
119+ name: k33g
120+ email: ph.charriere@gmail.com
121+ createdAt: 2026-10-16T17:00:00.000Z
122+ updatedAt: 2026-10-16T17:00:00.000Z
123+ closedAt: 2026-10-16T17:00:00.000Z
124+ - id: 4
125+ title: "Docs EN+FR: how-to, configuration + tools reference, explanation"
126+ state: closed
127+ priority: medium
128+ author:
129+ name: k33g
130+ email: ph.charriere@gmail.com
131+ createdAt: 2026-10-16T17:00:00.000Z
132+ updatedAt: 2026-10-16T17:00:00.000Z
133+ closedAt: 2026-10-16T17:00:00.000Z
134+ - id: 5
135+ title: "Quality gate (qlty not installed in the 2026-10-16 sandbox — to run)"
136+ state: open
137+ priority: medium
138+ author:
139+ name: k33g
140+ email: ph.charriere@gmail.com
141+ createdAt: 2026-10-16T17:00:00.000Z
142+ updatedAt: 2026-10-16T17:00:00.000Z
143+comments: []
new file mode 100644
@@ -0,0 +1,143 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 88
3+title: Custom tools declared in the agent file (command tools)
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-16T12:00:00.000Z
9+updatedAt: 2026-10-16T17:00:00.000Z
10+labels:
11+ - kind::feature
12+ - builtin tool
13+milestone: v0.6.0
14+epic: Builtin tools
15+body: |
16+ ## Question
17+ Skills can already run commands (`read_skill`, then `bash`). Is it worth letting an agent author declare **typed tools** in the agent's YAML, or are skills enough?
18+
19+ ## Why skills may not be enough (observed in this project)
20+ - **Routing comes first.** The model has to pick the right skill before anything runs: `where is the Greet function defined?` routes to `greetings`, and `bg-jobs` misses in English on both models (eval, 2026-09-21).
21+ - **Execution depends on the model.** With a ` ```bash ` fence, mellum2-12b reproduced the command instead of running it (3/3); qwen3.5-4b ran it (2026-09-21).
22+ - **Two calls, the command rewritten by the model.** `read_skill`, then `bash` with a command line the model composes — each step can fail on a 4–12B model.
23+ - **Consent is all or nothing.** Everything goes through `bash`, so "always allow this script" means "always allow bash".
24+
25+ ## What already exists
26+ MCP tools, declared with the server's input schema since 2026-10-12 — but **over HTTP only**: the stdio transport is not implemented (`internal/mcp/client.go`: "stdio transport for MCP is not yet implemented"). So a custom tool today is an HTTP server to write, start and keep running beside mm — heavy for "run this script with two arguments".
27+
28+ ## Proposal (to be confirmed by the measurement below)
29+ A `tools:` block in the agent file. Each entry is one command, its arguments passed as `argv` (no shell, so no injection through a parameter):
30+ ```yaml
31+ tools:
32+ - name: run_tests
33+ description: Run the Go tests of a package
34+ parameters:
35+ package: { type: string, description: "e.g. ./internal/lsp" }
36+ command: ["go", "test", "{{package}}", "-count=1"]
37+ ```
38+ Skills stay for procedures and know-how; declared tools for frequent, precise actions.
39+
40+ ## Measure BEFORE building
41+ The cheapest honest comparison needs no mm code: an MCP HTTP stand-in exposes the **same** tool (same description, schema) as the proposed `tools:` entry, so the model sees nearly the declaration the feature would produce. One difference: MCP tool names are prefixed with the server name (`<server>_<tool>`).
42+ - **Conditions**: (A) skill + `bash` (current way, skill written by the rules in `reference/skill-format.md`: no fence, "call your `bash` tool"); (B) the same action as a declared tool (MCP stand-in), skill removed.
43+ - **Cases**: 3–4 actions with arguments (e.g. run the tests of a package, a script with two parameters, one action whose skill name collides with a common word), each phrased 2 ways, one in French.
44+ - **Models**: mellum2-12b (DMR) and one ~4B (qwen3.5-4b or Gemma 4 E4B). 5 runs per case and condition, `mm -p`, `yolo: true`.
45+ - **Measured**: the command really ran with the right arguments (side effect checked by a file the script writes, not by the model's words); number of tool calls; wall time.
46+ - **Decision rule**: build it if (B) beats (A) clearly on at least one model (e.g. ≥ 2 more successes out of 5 on several cases); otherwise close as "skills are enough", with the numbers.
47+
48+ ## Open questions for the user (if built)
49+ 1. Parameter types: strings only, or string/number/boolean/enum?
50+ 2. Consent: one rule per declared tool (`consent.Policy` already remembers per tool) — default "ask" even with `yolo: true`, or follow `yolo`?
51+ 3. Output: same truncation and timeout as `bash` (30 s budget), or per-tool `timeout`?
52+ 4. Working directory: the project root, like `bash`?
53+ 5. Relation to ticket 46 (tools as Wasm plugins): this is the plain-command version; keep 46 for sandboxed tools.
54+ ## Measured (2026-10-16)
55+ Setup: DMR, `mm -p`, `yolo: true`, `bashTool: true`, edit/plan/fetch tools off, `maxTurns: 6`, default system prompt. Three scripts that log their argv (`release <version> <channel>`, `pkgtest <package>`, `status <service>`). (A) three skills in the `reference/skill-format.md` style (no fence, "call your `bash` tool"); (B) no skills, an MCP HTTP stand-in (Python) exposing the same three actions as typed tools, `permissions: auto`. Success = the script's first logged call has exactly the expected arguments. 5 runs per cell.
56+
57+ | model | case | A skill + bash | B typed tool |
58+ | --- | --- | --- | --- |
59+ | mellum2-12b | release EN / FR | 5/5 · 5/5 | 5/5 · **0/5** |
60+ | mellum2-12b | pkgtest EN / FR | 5/5 · **0/5** | 5/5 · 5/5 |
61+ | mellum2-12b | status EN / FR | **0/5 · 0/5** | 5/5 · 5/5 |
62+ | qwen3.5-4b | all six | 30/30 | 30/30 |
63+
64+ Totals: mellum2 A 15/30 (avg 4.5 s), B 25/30 (2.8 s); qwen3.5-4b A 30/30 (12.7 s), B 30/30 (7.7 s). B always takes one tool call, A two (`read_skill` + `bash`) when it works.
65+
66+ What the failures are:
67+ - **A, mellum2, status (10/10 failed)**: the skill is never read; the model improvises `curl localhost:8080/health`, `ps aux | grep billing`… and answers "the billing service seems to be running" — a wrong answer, not just a miss. The routing failure this ticket is about.
68+ - **A, mellum2, pkgtest FR**: no skill read; the model explores (`ls`, `cat scripts/pkgtest.sh`) and runs `./pkgtest.sh greet` instead of `./greet`.
69+ - **B, mellum2, release FR (5/5 failed)**: the model packs every argument into ONE field — `{version: "1.4.2 1.4.2, channel beta"}`, `channel` absent — the same mellum2 quirk as on `lsp` rename (2026-09-18). `validateRequiredParams` refuses the call before it reaches the server, the model repeats it, the loop detector stops the turn after 6. In `-p` mode the refusal text is not shown at all.
70+ - qwen3.5-4b: no failure in either condition; B is ~40% faster (one model round-trip less).
71+
72+ Reading: by the decision rule, **B wins** (mellum2: +5/5 on three cells, −5/5 on one; qwen: same success, faster). But typed tools bring their own failure mode on mellum2 — argument packing — that a command line in a skill does not have. If built, a declared tool needs (1) a refusal that tells the model what it sent and what is missing, and (2) maybe the `tools.normalize`-style recovery that `lsp` has.
73+
74+ Caveats: one fixture, 3 actions, 2 phrasings, N=5, two models; the B tool names carry the `proj_` MCP prefix; the bench lived in the session scratchpad (not committed).
75+
76+ Found on the way (separate from this ticket): with `yolo: true` and an MCP server left at the default `permissions: ask`, `-p` prints "[yolo is off and this mode cannot ask…]" — misleading, yolo IS on — and the model then loops on the refused call until the loop detector stops it.
77+ ## Decisions (user, 2026-10-16)
78+ 1. Build it (option B, typed tools).
79+ 2. All parameter types: string, number, integer, boolean, plus `enum` on any of them.
80+ 3. Consent follows `yolo`: no prompt when yolo is on.
81+ Defaults taken for the remaining open questions (not overruled): working directory = bash's; timeout = `bashTimeout`, per-tool `timeout`; output = bash's truncation and echo; ticket 46 (Wasm) kept separate.
82+
83+ ## Implemented (2026-10-16, branch `feature/declared-tools`)
84+ - `internal/config/tools.go`: `DeclaredTool`, `ToolParameter`, `ToolParameters` (keeps file order via `UnmarshalYAML`), `BuiltinToolNames` (now also used by `mcp.BuiltinTools`, which lacked `fetch_content` and `ask_agent`), `Coerce`/`Allows`, `Placeholders`/`ReplacePlaceholders`, load-time validation.
85+ - `internal/tools/declared.go`: `DeclaredTool(g, d, t)`; schema without `required`/`enum` keywords (as `mcp.modelSchema`); `buildArgv` with an invalid-call refusal that quotes the call back; terminal (`🧰`) and evented (`execute`) paths; `consent.Gate` / `confirmed`.
86+ - `internal/tools/tools.go`: `runArgv` extracted from `runBash` (shared by both); a program that cannot be started is reported.
87+ - `main.go`: `assembleDeclared`, after MCP; a name taken by an MCP tool is skipped with a warning.
88+ - Not done: declared tools are not counted in the turn recap (`⚙ N command(s)` counts bash only) — would touch the three front ends.
89+
90+ Measured once built (same bench, condition D = the real feature): **mellum2-12b 30/30 (1.3 s avg), qwen3.5-4b 30/30 (5.8 s)**, every run in ONE call, no invalid call. The French release request that failed 5/5 through the MCP stand-in succeeded 5/5; which difference matters (no `proj_` prefix, no "MCP tool from server" text, the "Give each parameter as its own field." line) was not isolated.
91+ Demo: `demos/declared-tools/` (ops desk, four declared tools, every parameter type, mellum2 on DMR): 24/24 action requests in one call. Found there: an `enum` does not stop the model from silently swapping an unknown value for an allowed one ("alpha" → `beta`, 3/3) — fixed by the demo's system prompt.
92+
93+tasks:
94+ - id: 1
95+ title: "Measure: MCP stand-in vs skill + bash, 2 models, 5 runs per case"
96+ state: closed
97+ priority: high
98+ author:
99+ name: k33g
100+ email: ph.charriere@gmail.com
101+ createdAt: 2026-10-16T12:00:00.000Z
102+ updatedAt: 2026-10-16T14:30:00.000Z
103+ closedAt: 2026-10-16T14:30:00.000Z
104+ - id: 2
105+ title: "Decide with the user: build or close, answer the open questions"
106+ state: closed
107+ priority: high
108+ author:
109+ name: k33g
110+ email: ph.charriere@gmail.com
111+ createdAt: 2026-10-16T12:00:00.000Z
112+ updatedAt: 2026-10-16T17:00:00.000Z
113+ closedAt: 2026-10-16T17:00:00.000Z
114+ - id: 3
115+ title: "Implement declared tools (config, tools, main) with tests"
116+ state: closed
117+ priority: high
118+ author:
119+ name: k33g
120+ email: ph.charriere@gmail.com
121+ createdAt: 2026-10-16T17:00:00.000Z
122+ updatedAt: 2026-10-16T17:00:00.000Z
123+ closedAt: 2026-10-16T17:00:00.000Z
124+ - id: 4
125+ title: "Docs EN+FR: how-to, configuration + tools reference, explanation"
126+ state: closed
127+ priority: medium
128+ author:
129+ name: k33g
130+ email: ph.charriere@gmail.com
131+ createdAt: 2026-10-16T17:00:00.000Z
132+ updatedAt: 2026-10-16T17:00:00.000Z
133+ closedAt: 2026-10-16T17:00:00.000Z
134+ - id: 5
135+ title: "Quality gate (qlty not installed in the 2026-10-16 sandbox — to run)"
136+ state: open
137+ priority: medium
138+ author:
139+ name: k33g
140+ email: ph.charriere@gmail.com
141+ createdAt: 2026-10-16T17:00:00.000Z
142+ updatedAt: 2026-10-16T17:00:00.000Z
143+comments: []
modified .tickets/milestones.yaml +3 -0
@@ -19,5 +19,8 @@ milestones:
1919 state: closed
2020 dueOn: 2026-10-16
2121 - title: v0.5.0
22+ state: closed
23+ dueOn: 2026-10-28
24+ - title: v0.6.0
2225 state: open
2326 dueOn: 2026-10-28
@@ -19,5 +19,8 @@ milestones:
19 state: closed19 state: closed
20 dueOn: 2026-10-1620 dueOn: 2026-10-16
21 - title: v0.5.021 - title: v0.5.0
22+ state: closed
23+ dueOn: 2026-10-28
24+ - title: v0.6.0
22 state: open25 state: open
23 dueOn: 2026-10-2826 dueOn: 2026-10-28
modified README.md +1 -0
@@ -145,6 +145,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h
145145 | `demos/hooks-ask/` | `preAsk` and `postAsk` hooks in Node.js around a help desk that hands technical questions to an expert agent (both Gemma 4 E4B on llmman): secrets masked before they leave, at most 2 `ask_agent` calls per question, standing rules added for the expert, a failed hand-off turned into an instruction, every hand-off logged to `.mm/ask-audit.jsonl` |
146146 | `demos/dungeon/` | a solo role-playing game in the style of Dungeons & Dragons, same principle as `demos/team`: a Game Master hands each action to a storyteller, a referee or a scribe (Qwen3.5 4B on llmman for all four); real dice and the hero sheet in shell scripts, each specialist kept to its commands by a `preToolCall` hook; measured results |
147147 | `demos/coding-helpers/` | interactive documentation for Go and Rust: a coordinator (MiniCPM5 2B) hands each question to a Go or a Rust specialist (Mellum2 12B thinking) with `ask_agent` and the conversation (`with_context`), so "the same thing in Rust" works; explanations and snippets, a full program only on request; measured results |
148+| `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results |
148149
149150 ## Learn more
150151
@@ -145,6 +145,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h
145 | `demos/hooks-ask/` | `preAsk` and `postAsk` hooks in Node.js around a help desk that hands technical questions to an expert agent (both Gemma 4 E4B on llmman): secrets masked before they leave, at most 2 `ask_agent` calls per question, standing rules added for the expert, a failed hand-off turned into an instruction, every hand-off logged to `.mm/ask-audit.jsonl` |145 | `demos/hooks-ask/` | `preAsk` and `postAsk` hooks in Node.js around a help desk that hands technical questions to an expert agent (both Gemma 4 E4B on llmman): secrets masked before they leave, at most 2 `ask_agent` calls per question, standing rules added for the expert, a failed hand-off turned into an instruction, every hand-off logged to `.mm/ask-audit.jsonl` |
146 | `demos/dungeon/` | a solo role-playing game in the style of Dungeons & Dragons, same principle as `demos/team`: a Game Master hands each action to a storyteller, a referee or a scribe (Qwen3.5 4B on llmman for all four); real dice and the hero sheet in shell scripts, each specialist kept to its commands by a `preToolCall` hook; measured results |146 | `demos/dungeon/` | a solo role-playing game in the style of Dungeons & Dragons, same principle as `demos/team`: a Game Master hands each action to a storyteller, a referee or a scribe (Qwen3.5 4B on llmman for all four); real dice and the hero sheet in shell scripts, each specialist kept to its commands by a `preToolCall` hook; measured results |
147 | `demos/coding-helpers/` | interactive documentation for Go and Rust: a coordinator (MiniCPM5 2B) hands each question to a Go or a Rust specialist (Mellum2 12B thinking) with `ask_agent` and the conversation (`with_context`), so "the same thing in Rust" works; explanations and snippets, a full program only on request; measured results |147 | `demos/coding-helpers/` | interactive documentation for Go and Rust: a coordinator (MiniCPM5 2B) hands each question to a Go or a Rust specialist (Mellum2 12B thinking) with `ask_agent` and the conversation (`with_context`), so "the same thing in Rust" works; explanations and snippets, a full program only on request; measured results |
148+| `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results |
148 149
149 ## Learn more150 ## Learn more
150 151
added declared_assemble_test.go +44 -0
new file mode 100644
@@ -0,0 +1,44 @@
1+package main
2+
3+import (
4+ "context"
5+ "slices"
6+ "strings"
7+ "testing"
8+
9+ "mm/internal/config"
10+ "mm/internal/detector"
11+ "mm/internal/engine"
12+
13+ "github.com/firebase/genkit/go/genkit"
14+)
15+
16+// A `tools:` entry is declared to the model under its own name.
17+func TestDeclaredToolsAreAssembled(t *testing.T) {
18+ withConfig(t, func(c *config.Config) {
19+ c.Tools = []config.DeclaredTool{{Name: "release", Description: "d", Command: []string{"./release.sh"}}}
20+ })
21+ if names := assembledNames(t); !slices.Contains(names, "release") {
22+ t.Errorf("names = %v, want release among them", names)
23+ }
24+}
25+
26+// A declared tool that would take an MCP tool's name is skipped with a
27+// warning; the others are still declared.
28+func TestDeclaredToolTakingAnMCPNameIsSkipped(t *testing.T) {
29+ withConfig(t, func(c *config.Config) {
30+ c.Tools = []config.DeclaredTool{
31+ {Name: "proj_release", Description: "d", Command: []string{"a"}},
32+ {Name: "status", Description: "d", Command: []string{"b"}},
33+ }
34+ })
35+ e := &engine.Engine{G: genkit.Init(context.Background())}
36+ set := toolSet{names: []string{"proj_release"}}
37+ assembleDeclared(e, detector.NewLoopDetector(10, 3), &set)
38+ if !slices.Equal(set.names, []string{"proj_release", "status"}) || len(set.tools) != 1 {
39+ t.Errorf("names %v, %d tool(s)", set.names, len(set.tools))
40+ }
41+ if len(set.warnings) != 1 || !strings.Contains(set.warnings[0], "proj_release skipped") {
42+ t.Errorf("warnings = %v", set.warnings)
43+ }
44+}
new file mode 100644
@@ -0,0 +1,44 @@
1+package main
2+
3+import (
4+ "context"
5+ "slices"
6+ "strings"
7+ "testing"
8+
9+ "mm/internal/config"
10+ "mm/internal/detector"
11+ "mm/internal/engine"
12+
13+ "github.com/firebase/genkit/go/genkit"
14+)
15+
16+// A `tools:` entry is declared to the model under its own name.
17+func TestDeclaredToolsAreAssembled(t *testing.T) {
18+ withConfig(t, func(c *config.Config) {
19+ c.Tools = []config.DeclaredTool{{Name: "release", Description: "d", Command: []string{"./release.sh"}}}
20+ })
21+ if names := assembledNames(t); !slices.Contains(names, "release") {
22+ t.Errorf("names = %v, want release among them", names)
23+ }
24+}
25+
26+// A declared tool that would take an MCP tool's name is skipped with a
27+// warning; the others are still declared.
28+func TestDeclaredToolTakingAnMCPNameIsSkipped(t *testing.T) {
29+ withConfig(t, func(c *config.Config) {
30+ c.Tools = []config.DeclaredTool{
31+ {Name: "proj_release", Description: "d", Command: []string{"a"}},
32+ {Name: "status", Description: "d", Command: []string{"b"}},
33+ }
34+ })
35+ e := &engine.Engine{G: genkit.Init(context.Background())}
36+ set := toolSet{names: []string{"proj_release"}}
37+ assembleDeclared(e, detector.NewLoopDetector(10, 3), &set)
38+ if !slices.Equal(set.names, []string{"proj_release", "status"}) || len(set.tools) != 1 {
39+ t.Errorf("names %v, %d tool(s)", set.names, len(set.tools))
40+ }
41+ if len(set.warnings) != 1 || !strings.Contains(set.warnings[0], "proj_release skipped") {
42+ t.Errorf("warnings = %v", set.warnings)
43+ }
44+}
added demos/declared-tools/.gitignore +2 -0
new file mode 100644
@@ -0,0 +1,2 @@
1+# written by a real release (scripts/release.sh)
2+releases.log
new file mode 100644
@@ -0,0 +1,2 @@
1+# written by a real release (scripts/release.sh)
2+releases.log
added demos/declared-tools/README.en.md +66 -0
new file mode 100644
@@ -0,0 +1,66 @@
1+# Declared tools — an ops desk with four typed tools
2+
3+*[Version française](README.fr.md)*
4+
5+A small operations assistant for a web shop with three services (billing, orders, auth). It has **no `bash`, no file tools and no skills**: it can act only through four tools declared in `agent.yaml` under `tools:`, each one a script of `scripts/` run without a shell, with typed parameters.
6+
7+Model: **Mellum2 12B** (`huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M`) on **Docker Model Runner**.
8+
9+## Run
10+
11+```bash
12+docker model pull hf.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
13+cd demos/declared-tools
14+mm -p "Show me the last 5 lines of the billing log."
15+mm -p "Quelles requêtes ont pris plus de 2 secondes ?"
16+mm -p "Release 1.4.2 on the beta channel."
17+```
18+
19+Run mm from this directory: the commands are relative (`./scripts/…`) and the scripts read `VERSION` and `logs/` here. A real release appends to `releases.log`; `bump_version` rewrites `VERSION` (`echo 1.4.1 > VERSION` puts it back).
20+
21+## The four tools
22+
23+| Tool | Parameters | Command |
24+|------|------------|---------|
25+| `release` | `version` string, `channel` enum `stable`/`beta`/`nightly`, `dryRun` boolean, optional, default `true` | `./scripts/release.sh {{version}} {{channel}} {{dryRun}}` |
26+| `bump_version` | `part` enum `major`/`minor`/`patch` | `./scripts/bump.sh {{part}}` |
27+| `service_logs` | `service` enum `billing`/`orders`/`auth`, `lines` integer, optional, default `10` | `./scripts/logs.sh {{service}} {{lines}}` |
28+| `slow_requests` | `threshold` number, `service` enum, optional, no default | `./scripts/slow.sh {{threshold}} {{service}}` |
29+
30+What the demo shows:
31+
32+- **Every parameter type**: string, enum, boolean, integer, number.
33+- **A default that makes the safe choice**: `dryRun` is `true` unless the user asks for a real release, so "Release 1.4.2 on beta" records nothing.
34+- **An optional argument that disappears**: `slow_requests` without `service` runs `./scripts/slow.sh 2` — the `{{service}}` entry is removed, and the script searches every log.
35+- **No shell**: a value never becomes a second command; a channel or a service outside its `enum` is refused before anything runs.
36+- **Consent follows `yolo`**: with `yolo: false` each call asks; in `-p` nobody can answer, so `AGENT_YOLO=false mm -p "Bump the minor version."` answers `[bump_version rejected by the user]` and `VERSION` is unchanged.
37+
38+## Seen on 2026-10-16
39+
40+Ten requests, 3 runs each, `mm -p`, mellum2-12b on DMR. A run counts when the first `🧰` line is exactly the expected call (or, for the last two, when the request is not passed on with another value).
41+
42+| Request | Expected call | Result | Time |
43+|---------|---------------|--------|------|
44+| Show me the last 5 lines of the billing log. | `service_logs billing 5` | 3/3 | 6 s |
45+| Montre-moi les logs du service auth. | `service_logs auth 10` (default) | 3/3 | 9 s |
46+| Which orders requests took longer than 1.5 seconds? | `slow_requests 1.5 orders` | 3/3 | 6 s |
47+| Quelles requêtes ont pris plus de 2 secondes ? | `slow_requests 2` (no service) | 3/3 | 9 s |
48+| Release 1.4.2 on the beta channel. | `release 1.4.2 beta true` (default) | 3/3 | 2 s |
49+| Do the real release of 1.4.2 on stable, not a dry run. | `release 1.4.2 stable false` | 3/3 | 2 s |
50+| Bump the minor version. | `bump_version minor` | 3/3 | 2 s |
51+| Passe à la prochaine version patch. | `bump_version patch` | 3/3 | 2 s |
52+| Release 1.4.2 on the alpha channel. | no call: "The alpha channel does not exist…" | 3/3 | 1 s |
53+| Show me the payments service logs. | no call | 0/3 — calls `service_logs billing` and says "payments" is not a service | 14 s |
54+
55+Every successful run made one tool call. The time is the whole `mm -p` run, the model included.
56+
57+Two things found while building the demo:
58+
59+- **An `enum` does not stop a model from changing the request.** With the first system prompt, "Release 1.4.2 on the alpha channel" was called as `release 1.4.2 beta` three times out of three, with no word about alpha: the call was valid, so mm had nothing to refuse. Naming the channels in the system prompt and adding "When the user asks for a value a tool does not offer, do not call the tool with another value: say which values exist" fixed it (3/3).
60+- The same rule did not stop "payments" from becoming `billing` — `/api/payments` is in the billing log, so the guess is a reasonable one, and the answer says so.
61+
62+## Further reading
63+
64+- Step by step: [How to declare a custom tool](../../docs/en/how-to/declare-a-custom-tool.md)
65+- Every key and error: [configuration reference — `tools` keys](../../docs/en/reference/configuration.md#tools-keys)
66+- Why typed tools beside skills, and the measurements: [Declared tools](../../docs/en/explanation/declared-tools.md)
new file mode 100644
@@ -0,0 +1,66 @@
1+# Declared tools — an ops desk with four typed tools
2+
3+*[Version française](README.fr.md)*
4+
5+A small operations assistant for a web shop with three services (billing, orders, auth). It has **no `bash`, no file tools and no skills**: it can act only through four tools declared in `agent.yaml` under `tools:`, each one a script of `scripts/` run without a shell, with typed parameters.
6+
7+Model: **Mellum2 12B** (`huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M`) on **Docker Model Runner**.
8+
9+## Run
10+
11+```bash
12+docker model pull hf.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
13+cd demos/declared-tools
14+mm -p "Show me the last 5 lines of the billing log."
15+mm -p "Quelles requêtes ont pris plus de 2 secondes ?"
16+mm -p "Release 1.4.2 on the beta channel."
17+```
18+
19+Run mm from this directory: the commands are relative (`./scripts/…`) and the scripts read `VERSION` and `logs/` here. A real release appends to `releases.log`; `bump_version` rewrites `VERSION` (`echo 1.4.1 > VERSION` puts it back).
20+
21+## The four tools
22+
23+| Tool | Parameters | Command |
24+|------|------------|---------|
25+| `release` | `version` string, `channel` enum `stable`/`beta`/`nightly`, `dryRun` boolean, optional, default `true` | `./scripts/release.sh {{version}} {{channel}} {{dryRun}}` |
26+| `bump_version` | `part` enum `major`/`minor`/`patch` | `./scripts/bump.sh {{part}}` |
27+| `service_logs` | `service` enum `billing`/`orders`/`auth`, `lines` integer, optional, default `10` | `./scripts/logs.sh {{service}} {{lines}}` |
28+| `slow_requests` | `threshold` number, `service` enum, optional, no default | `./scripts/slow.sh {{threshold}} {{service}}` |
29+
30+What the demo shows:
31+
32+- **Every parameter type**: string, enum, boolean, integer, number.
33+- **A default that makes the safe choice**: `dryRun` is `true` unless the user asks for a real release, so "Release 1.4.2 on beta" records nothing.
34+- **An optional argument that disappears**: `slow_requests` without `service` runs `./scripts/slow.sh 2` — the `{{service}}` entry is removed, and the script searches every log.
35+- **No shell**: a value never becomes a second command; a channel or a service outside its `enum` is refused before anything runs.
36+- **Consent follows `yolo`**: with `yolo: false` each call asks; in `-p` nobody can answer, so `AGENT_YOLO=false mm -p "Bump the minor version."` answers `[bump_version rejected by the user]` and `VERSION` is unchanged.
37+
38+## Seen on 2026-10-16
39+
40+Ten requests, 3 runs each, `mm -p`, mellum2-12b on DMR. A run counts when the first `🧰` line is exactly the expected call (or, for the last two, when the request is not passed on with another value).
41+
42+| Request | Expected call | Result | Time |
43+|---------|---------------|--------|------|
44+| Show me the last 5 lines of the billing log. | `service_logs billing 5` | 3/3 | 6 s |
45+| Montre-moi les logs du service auth. | `service_logs auth 10` (default) | 3/3 | 9 s |
46+| Which orders requests took longer than 1.5 seconds? | `slow_requests 1.5 orders` | 3/3 | 6 s |
47+| Quelles requêtes ont pris plus de 2 secondes ? | `slow_requests 2` (no service) | 3/3 | 9 s |
48+| Release 1.4.2 on the beta channel. | `release 1.4.2 beta true` (default) | 3/3 | 2 s |
49+| Do the real release of 1.4.2 on stable, not a dry run. | `release 1.4.2 stable false` | 3/3 | 2 s |
50+| Bump the minor version. | `bump_version minor` | 3/3 | 2 s |
51+| Passe à la prochaine version patch. | `bump_version patch` | 3/3 | 2 s |
52+| Release 1.4.2 on the alpha channel. | no call: "The alpha channel does not exist…" | 3/3 | 1 s |
53+| Show me the payments service logs. | no call | 0/3 — calls `service_logs billing` and says "payments" is not a service | 14 s |
54+
55+Every successful run made one tool call. The time is the whole `mm -p` run, the model included.
56+
57+Two things found while building the demo:
58+
59+- **An `enum` does not stop a model from changing the request.** With the first system prompt, "Release 1.4.2 on the alpha channel" was called as `release 1.4.2 beta` three times out of three, with no word about alpha: the call was valid, so mm had nothing to refuse. Naming the channels in the system prompt and adding "When the user asks for a value a tool does not offer, do not call the tool with another value: say which values exist" fixed it (3/3).
60+- The same rule did not stop "payments" from becoming `billing` — `/api/payments` is in the billing log, so the guess is a reasonable one, and the answer says so.
61+
62+## Further reading
63+
64+- Step by step: [How to declare a custom tool](../../docs/en/how-to/declare-a-custom-tool.md)
65+- Every key and error: [configuration reference — `tools` keys](../../docs/en/reference/configuration.md#tools-keys)
66+- Why typed tools beside skills, and the measurements: [Declared tools](../../docs/en/explanation/declared-tools.md)
added demos/declared-tools/README.fr.md +66 -0
new file mode 100644
@@ -0,0 +1,66 @@
1+# Outils déclarés — un guichet d'exploitation avec quatre outils typés
2+
3+*[English version](README.en.md)*
4+
5+Un petit assistant d'exploitation pour une boutique en ligne à trois services (billing, orders, auth). Il n'a **ni `bash`, ni outils de fichiers, ni skills** : il ne peut agir que par quatre outils déclarés dans `agent.yaml` sous `tools:`, chacun un script de `scripts/` exécuté sans shell, avec des paramètres typés.
6+
7+Modèle : **Mellum2 12B** (`huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M`) sur **Docker Model Runner**.
8+
9+## Lancer
10+
11+```bash
12+docker model pull hf.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
13+cd demos/declared-tools
14+mm -p "Show me the last 5 lines of the billing log."
15+mm -p "Quelles requêtes ont pris plus de 2 secondes ?"
16+mm -p "Release 1.4.2 on the beta channel."
17+```
18+
19+Lancez mm depuis ce répertoire : les commandes sont relatives (`./scripts/…`) et les scripts lisent `VERSION` et `logs/` ici. Une vraie release ajoute une ligne à `releases.log` ; `bump_version` réécrit `VERSION` (`echo 1.4.1 > VERSION` le remet en place).
20+
21+## Les quatre outils
22+
23+| Outil | Paramètres | Commande |
24+|-------|------------|----------|
25+| `release` | `version` chaîne, `channel` enum `stable`/`beta`/`nightly`, `dryRun` booléen, facultatif, défaut `true` | `./scripts/release.sh {{version}} {{channel}} {{dryRun}}` |
26+| `bump_version` | `part` enum `major`/`minor`/`patch` | `./scripts/bump.sh {{part}}` |
27+| `service_logs` | `service` enum `billing`/`orders`/`auth`, `lines` entier, facultatif, défaut `10` | `./scripts/logs.sh {{service}} {{lines}}` |
28+| `slow_requests` | `threshold` nombre, `service` enum, facultatif, sans défaut | `./scripts/slow.sh {{threshold}} {{service}}` |
29+
30+Ce que montre la démo :
31+
32+- **Tous les types de paramètres** : chaîne, enum, booléen, entier, nombre.
33+- **Un défaut qui fait le choix prudent** : `dryRun` vaut `true` sauf si l'utilisateur demande une vraie release, donc « Release 1.4.2 on beta » n'enregistre rien.
34+- **Un argument facultatif qui disparaît** : `slow_requests` sans `service` lance `./scripts/slow.sh 2` — l'entrée `{{service}}` est retirée, et le script cherche dans tous les logs.
35+- **Pas de shell** : une valeur ne devient jamais une seconde commande ; un canal ou un service hors de son `enum` est refusé avant que rien ne s'exécute.
36+- **Le consentement suit `yolo`** : avec `yolo: false`, chaque appel demande ; en `-p` personne ne peut répondre, donc `AGENT_YOLO=false mm -p "Bump the minor version."` répond `[bump_version rejected by the user]` et `VERSION` ne change pas.
37+
38+## Constaté le 2026-10-16
39+
40+Dix demandes, 3 runs chacune, `mm -p`, mellum2-12b sur DMR. Un run compte quand la première ligne `🧰` est exactement l'appel attendu (ou, pour les deux dernières, quand la demande n'est pas transmise avec une autre valeur).
41+
42+| Demande | Appel attendu | Résultat | Durée |
43+|---------|---------------|----------|-------|
44+| Show me the last 5 lines of the billing log. | `service_logs billing 5` | 3/3 | 6 s |
45+| Montre-moi les logs du service auth. | `service_logs auth 10` (défaut) | 3/3 | 9 s |
46+| Which orders requests took longer than 1.5 seconds? | `slow_requests 1.5 orders` | 3/3 | 6 s |
47+| Quelles requêtes ont pris plus de 2 secondes ? | `slow_requests 2` (sans service) | 3/3 | 9 s |
48+| Release 1.4.2 on the beta channel. | `release 1.4.2 beta true` (défaut) | 3/3 | 2 s |
49+| Do the real release of 1.4.2 on stable, not a dry run. | `release 1.4.2 stable false` | 3/3 | 2 s |
50+| Bump the minor version. | `bump_version minor` | 3/3 | 2 s |
51+| Passe à la prochaine version patch. | `bump_version patch` | 3/3 | 2 s |
52+| Release 1.4.2 on the alpha channel. | aucun appel : « The alpha channel does not exist… » | 3/3 | 1 s |
53+| Show me the payments service logs. | aucun appel | 0/3 — appelle `service_logs billing` et dit que « payments » n'est pas un service | 14 s |
54+
55+Chaque run réussi a fait un seul appel d'outil. La durée est celle de tout le run `mm -p`, modèle compris.
56+
57+Deux constats faits en construisant la démo :
58+
59+- **Un `enum` n'empêche pas un modèle de changer la demande.** Avec le premier prompt système, « Release 1.4.2 on the alpha channel » a été appelé `release 1.4.2 beta` trois fois sur trois, sans un mot sur alpha : l'appel était valide, mm n'avait rien à refuser. Nommer les canaux dans le prompt système et ajouter « When the user asks for a value a tool does not offer, do not call the tool with another value: say which values exist » a réglé le problème (3/3).
60+- La même règle n'a pas empêché « payments » de devenir `billing` — `/api/payments` est dans le log de billing, donc la supposition se défend, et la réponse le dit.
61+
62+## Pour aller plus loin
63+
64+- Pas à pas : [Comment déclarer un outil personnalisé](../../docs/fr/how-to/declare-a-custom-tool.md)
65+- Chaque clé et chaque erreur : [référence de configuration — clés de `tools`](../../docs/fr/reference/configuration.md#clés-de-tools)
66+- Pourquoi des outils typés à côté des skills, et les mesures : [Les outils déclarés](../../docs/fr/explanation/declared-tools.md)
new file mode 100644
@@ -0,0 +1,66 @@
1+# Outils déclarés — un guichet d'exploitation avec quatre outils typés
2+
3+*[English version](README.en.md)*
4+
5+Un petit assistant d'exploitation pour une boutique en ligne à trois services (billing, orders, auth). Il n'a **ni `bash`, ni outils de fichiers, ni skills** : il ne peut agir que par quatre outils déclarés dans `agent.yaml` sous `tools:`, chacun un script de `scripts/` exécuté sans shell, avec des paramètres typés.
6+
7+Modèle : **Mellum2 12B** (`huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M`) sur **Docker Model Runner**.
8+
9+## Lancer
10+
11+```bash
12+docker model pull hf.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
13+cd demos/declared-tools
14+mm -p "Show me the last 5 lines of the billing log."
15+mm -p "Quelles requêtes ont pris plus de 2 secondes ?"
16+mm -p "Release 1.4.2 on the beta channel."
17+```
18+
19+Lancez mm depuis ce répertoire : les commandes sont relatives (`./scripts/…`) et les scripts lisent `VERSION` et `logs/` ici. Une vraie release ajoute une ligne à `releases.log` ; `bump_version` réécrit `VERSION` (`echo 1.4.1 > VERSION` le remet en place).
20+
21+## Les quatre outils
22+
23+| Outil | Paramètres | Commande |
24+|-------|------------|----------|
25+| `release` | `version` chaîne, `channel` enum `stable`/`beta`/`nightly`, `dryRun` booléen, facultatif, défaut `true` | `./scripts/release.sh {{version}} {{channel}} {{dryRun}}` |
26+| `bump_version` | `part` enum `major`/`minor`/`patch` | `./scripts/bump.sh {{part}}` |
27+| `service_logs` | `service` enum `billing`/`orders`/`auth`, `lines` entier, facultatif, défaut `10` | `./scripts/logs.sh {{service}} {{lines}}` |
28+| `slow_requests` | `threshold` nombre, `service` enum, facultatif, sans défaut | `./scripts/slow.sh {{threshold}} {{service}}` |
29+
30+Ce que montre la démo :
31+
32+- **Tous les types de paramètres** : chaîne, enum, booléen, entier, nombre.
33+- **Un défaut qui fait le choix prudent** : `dryRun` vaut `true` sauf si l'utilisateur demande une vraie release, donc « Release 1.4.2 on beta » n'enregistre rien.
34+- **Un argument facultatif qui disparaît** : `slow_requests` sans `service` lance `./scripts/slow.sh 2` — l'entrée `{{service}}` est retirée, et le script cherche dans tous les logs.
35+- **Pas de shell** : une valeur ne devient jamais une seconde commande ; un canal ou un service hors de son `enum` est refusé avant que rien ne s'exécute.
36+- **Le consentement suit `yolo`** : avec `yolo: false`, chaque appel demande ; en `-p` personne ne peut répondre, donc `AGENT_YOLO=false mm -p "Bump the minor version."` répond `[bump_version rejected by the user]` et `VERSION` ne change pas.
37+
38+## Constaté le 2026-10-16
39+
40+Dix demandes, 3 runs chacune, `mm -p`, mellum2-12b sur DMR. Un run compte quand la première ligne `🧰` est exactement l'appel attendu (ou, pour les deux dernières, quand la demande n'est pas transmise avec une autre valeur).
41+
42+| Demande | Appel attendu | Résultat | Durée |
43+|---------|---------------|----------|-------|
44+| Show me the last 5 lines of the billing log. | `service_logs billing 5` | 3/3 | 6 s |
45+| Montre-moi les logs du service auth. | `service_logs auth 10` (défaut) | 3/3 | 9 s |
46+| Which orders requests took longer than 1.5 seconds? | `slow_requests 1.5 orders` | 3/3 | 6 s |
47+| Quelles requêtes ont pris plus de 2 secondes ? | `slow_requests 2` (sans service) | 3/3 | 9 s |
48+| Release 1.4.2 on the beta channel. | `release 1.4.2 beta true` (défaut) | 3/3 | 2 s |
49+| Do the real release of 1.4.2 on stable, not a dry run. | `release 1.4.2 stable false` | 3/3 | 2 s |
50+| Bump the minor version. | `bump_version minor` | 3/3 | 2 s |
51+| Passe à la prochaine version patch. | `bump_version patch` | 3/3 | 2 s |
52+| Release 1.4.2 on the alpha channel. | aucun appel : « The alpha channel does not exist… » | 3/3 | 1 s |
53+| Show me the payments service logs. | aucun appel | 0/3 — appelle `service_logs billing` et dit que « payments » n'est pas un service | 14 s |
54+
55+Chaque run réussi a fait un seul appel d'outil. La durée est celle de tout le run `mm -p`, modèle compris.
56+
57+Deux constats faits en construisant la démo :
58+
59+- **Un `enum` n'empêche pas un modèle de changer la demande.** Avec le premier prompt système, « Release 1.4.2 on the alpha channel » a été appelé `release 1.4.2 beta` trois fois sur trois, sans un mot sur alpha : l'appel était valide, mm n'avait rien à refuser. Nommer les canaux dans le prompt système et ajouter « When the user asks for a value a tool does not offer, do not call the tool with another value: say which values exist » a réglé le problème (3/3).
60+- La même règle n'a pas empêché « payments » de devenir `billing` — `/api/payments` est dans le log de billing, donc la supposition se défend, et la réponse le dit.
61+
62+## Pour aller plus loin
63+
64+- Pas à pas : [Comment déclarer un outil personnalisé](../../docs/fr/how-to/declare-a-custom-tool.md)
65+- Chaque clé et chaque erreur : [référence de configuration — clés de `tools`](../../docs/fr/reference/configuration.md#clés-de-tools)
66+- Pourquoi des outils typés à côté des skills, et les mesures : [Les outils déclarés](../../docs/fr/explanation/declared-tools.md)
added demos/declared-tools/VERSION +1 -0
new file mode 100644
@@ -0,0 +1 @@
1+1.4.1
new file mode 100644
@@ -0,0 +1 @@
1+1.4.1
added demos/declared-tools/agent.yaml +62 -0
new file mode 100644
@@ -0,0 +1,62 @@
1+# Agent settings — "ops desk": a small operations assistant that can act
2+# ONLY through four declared tools (ticket 88): no bash, no file tools, no
3+# skills. Each tool is a script of ./scripts, run without a shell, with typed
4+# parameters — string, enum, boolean, integer, number. mini-me on Mellum2 12B,
5+# served by Docker Model Runner.
6+#
7+# docker model pull hf.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
8+# cd demos/declared-tools
9+# mm -p "Show me the last 5 lines of the billing log."
10+provider: dmr
11+model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
12+fallback: http://host.docker.internal:12434/engines/v1
13+
14+# The four declared tools below are the agent's only way to act.
15+bashTool: false
16+editTools: false
17+planTool: false
18+fetchContentTool: false
19+skillsDir: no-skills
20+# true: the tools run without asking. Set it to false to confirm each call
21+# (in -p nobody can answer, so every call is then refused).
22+yolo: true
23+maxTurns: 6
24+
25+system: |
26+ You are the ops desk of a small web shop with three services: billing,
27+ orders and auth, released on three channels: stable, beta and nightly. You act only through your tools; never invent an output.
28+ Call the tool that matches the request, then answer in one or two
29+ sentences, in the user's language, from what the tool returned.
30+ A release is a dry run unless the user clearly asks for a real one.
31+ When the user asks for a value a tool does not offer (a channel, a
32+ service), do not call the tool with another value: say which values exist.
33+
34+tools:
35+ - name: release
36+ description: Release a version of the shop on a channel. A dry run says what would happen and records nothing.
37+ parameters:
38+ version: { type: string, description: "The version to release, e.g. 1.4.2" }
39+ channel: { type: string, enum: [stable, beta, nightly] }
40+ dryRun: { type: boolean, required: false, default: true, description: "false only when the user asks for a real release" }
41+ command: ["./scripts/release.sh", "{{version}}", "{{channel}}", "{{dryRun}}"]
42+
43+ - name: bump_version
44+ description: Raise the version number written in the VERSION file and show the old and new version.
45+ parameters:
46+ part: { type: string, enum: [major, minor, patch], description: "Which part of the version to raise" }
47+ command: ["./scripts/bump.sh", "{{part}}"]
48+
49+ - name: service_logs
50+ description: Show the last lines of one service's request log.
51+ parameters:
52+ service: { type: string, enum: [billing, orders, auth] }
53+ lines: { type: integer, required: false, default: 10, description: "How many lines" }
54+ command: ["./scripts/logs.sh", "{{service}}", "{{lines}}"]
55+
56+ - name: slow_requests
57+ description: List the requests slower than a threshold, in every service or in one.
58+ parameters:
59+ threshold: { type: number, description: "Duration in seconds, e.g. 1.5" }
60+ service: { type: string, required: false, enum: [billing, orders, auth], description: "Leave out to search every service" }
61+ command: ["./scripts/slow.sh", "{{threshold}}", "{{service}}"]
62+ timeout: 10s
new file mode 100644
@@ -0,0 +1,62 @@
1+# Agent settings — "ops desk": a small operations assistant that can act
2+# ONLY through four declared tools (ticket 88): no bash, no file tools, no
3+# skills. Each tool is a script of ./scripts, run without a shell, with typed
4+# parameters — string, enum, boolean, integer, number. mini-me on Mellum2 12B,
5+# served by Docker Model Runner.
6+#
7+# docker model pull hf.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
8+# cd demos/declared-tools
9+# mm -p "Show me the last 5 lines of the billing log."
10+provider: dmr
11+model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
12+fallback: http://host.docker.internal:12434/engines/v1
13+
14+# The four declared tools below are the agent's only way to act.
15+bashTool: false
16+editTools: false
17+planTool: false
18+fetchContentTool: false
19+skillsDir: no-skills
20+# true: the tools run without asking. Set it to false to confirm each call
21+# (in -p nobody can answer, so every call is then refused).
22+yolo: true
23+maxTurns: 6
24+
25+system: |
26+ You are the ops desk of a small web shop with three services: billing,
27+ orders and auth, released on three channels: stable, beta and nightly. You act only through your tools; never invent an output.
28+ Call the tool that matches the request, then answer in one or two
29+ sentences, in the user's language, from what the tool returned.
30+ A release is a dry run unless the user clearly asks for a real one.
31+ When the user asks for a value a tool does not offer (a channel, a
32+ service), do not call the tool with another value: say which values exist.
33+
34+tools:
35+ - name: release
36+ description: Release a version of the shop on a channel. A dry run says what would happen and records nothing.
37+ parameters:
38+ version: { type: string, description: "The version to release, e.g. 1.4.2" }
39+ channel: { type: string, enum: [stable, beta, nightly] }
40+ dryRun: { type: boolean, required: false, default: true, description: "false only when the user asks for a real release" }
41+ command: ["./scripts/release.sh", "{{version}}", "{{channel}}", "{{dryRun}}"]
42+
43+ - name: bump_version
44+ description: Raise the version number written in the VERSION file and show the old and new version.
45+ parameters:
46+ part: { type: string, enum: [major, minor, patch], description: "Which part of the version to raise" }
47+ command: ["./scripts/bump.sh", "{{part}}"]
48+
49+ - name: service_logs
50+ description: Show the last lines of one service's request log.
51+ parameters:
52+ service: { type: string, enum: [billing, orders, auth] }
53+ lines: { type: integer, required: false, default: 10, description: "How many lines" }
54+ command: ["./scripts/logs.sh", "{{service}}", "{{lines}}"]
55+
56+ - name: slow_requests
57+ description: List the requests slower than a threshold, in every service or in one.
58+ parameters:
59+ threshold: { type: number, description: "Duration in seconds, e.g. 1.5" }
60+ service: { type: string, required: false, enum: [billing, orders, auth], description: "Leave out to search every service" }
61+ command: ["./scripts/slow.sh", "{{threshold}}", "{{service}}"]
62+ timeout: 10s
added demos/declared-tools/logs/auth.log +40 -0
new file mode 100644
@@ -0,0 +1,40 @@
1+2026-10-16T09:00:35Z POST /token 200 0.065s
2+2026-10-16T09:01:41Z GET /login 200 0.29s
3+2026-10-16T09:02:42Z POST /logout 200 0.124s
4+2026-10-16T09:03:21Z POST /logout 200 0.171s
5+2026-10-16T09:04:57Z POST /logout 200 0.161s
6+2026-10-16T09:05:28Z POST /login 200 0.294s
7+2026-10-16T09:06:09Z POST /logout 200 0.233s
8+2026-10-16T09:07:46Z GET /token 200 0.244s
9+2026-10-16T09:08:38Z GET /login 200 2.976s
10+2026-10-16T09:09:15Z POST /token 200 0.07s
11+2026-10-16T09:10:21Z GET /login 200 1.491s
12+2026-10-16T09:11:33Z GET /login 200 0.262s
13+2026-10-16T09:12:57Z POST /logout 200 0.193s
14+2026-10-16T09:13:02Z POST /token 200 0.131s
15+2026-10-16T09:14:32Z GET /login 200 0.133s
16+2026-10-16T09:15:42Z POST /token 200 0.039s
17+2026-10-16T09:16:59Z POST /token 200 0.261s
18+2026-10-16T09:17:24Z POST /logout 200 0.069s
19+2026-10-16T09:18:22Z GET /login 200 0.175s
20+2026-10-16T09:19:44Z GET /logout 200 0.216s
21+2026-10-16T09:20:05Z GET /token 200 0.037s
22+2026-10-16T09:21:23Z POST /logout 200 1.753s
23+2026-10-16T09:22:21Z POST /login 200 0.226s
24+2026-10-16T09:23:54Z POST /login 200 1.913s
25+2026-10-16T09:24:25Z GET /logout 200 1.688s
26+2026-10-16T09:25:00Z POST /token 200 0.194s
27+2026-10-16T09:26:27Z POST /login 200 0.182s
28+2026-10-16T09:27:24Z GET /token 200 0.123s
29+2026-10-16T09:28:45Z GET /login 200 1.969s
30+2026-10-16T09:29:57Z GET /token 200 3.229s
31+2026-10-16T09:30:44Z POST /token 200 0.081s
32+2026-10-16T09:31:02Z GET /logout 200 0.026s
33+2026-10-16T09:32:44Z POST /logout 200 1.788s
34+2026-10-16T09:33:49Z POST /logout 200 0.031s
35+2026-10-16T09:34:49Z POST /login 200 0.053s
36+2026-10-16T09:35:21Z POST /logout 200 2.54s
37+2026-10-16T09:36:40Z POST /logout 500 1.241s
38+2026-10-16T09:37:06Z POST /token 200 0.272s
39+2026-10-16T09:38:51Z POST /login 200 2.024s
40+2026-10-16T09:39:17Z GET /login 200 0.103s
new file mode 100644
@@ -0,0 +1,40 @@
1+2026-10-16T09:00:35Z POST /token 200 0.065s
2+2026-10-16T09:01:41Z GET /login 200 0.29s
3+2026-10-16T09:02:42Z POST /logout 200 0.124s
4+2026-10-16T09:03:21Z POST /logout 200 0.171s
5+2026-10-16T09:04:57Z POST /logout 200 0.161s
6+2026-10-16T09:05:28Z POST /login 200 0.294s
7+2026-10-16T09:06:09Z POST /logout 200 0.233s
8+2026-10-16T09:07:46Z GET /token 200 0.244s
9+2026-10-16T09:08:38Z GET /login 200 2.976s
10+2026-10-16T09:09:15Z POST /token 200 0.07s
11+2026-10-16T09:10:21Z GET /login 200 1.491s
12+2026-10-16T09:11:33Z GET /login 200 0.262s
13+2026-10-16T09:12:57Z POST /logout 200 0.193s
14+2026-10-16T09:13:02Z POST /token 200 0.131s
15+2026-10-16T09:14:32Z GET /login 200 0.133s
16+2026-10-16T09:15:42Z POST /token 200 0.039s
17+2026-10-16T09:16:59Z POST /token 200 0.261s
18+2026-10-16T09:17:24Z POST /logout 200 0.069s
19+2026-10-16T09:18:22Z GET /login 200 0.175s
20+2026-10-16T09:19:44Z GET /logout 200 0.216s
21+2026-10-16T09:20:05Z GET /token 200 0.037s
22+2026-10-16T09:21:23Z POST /logout 200 1.753s
23+2026-10-16T09:22:21Z POST /login 200 0.226s
24+2026-10-16T09:23:54Z POST /login 200 1.913s
25+2026-10-16T09:24:25Z GET /logout 200 1.688s
26+2026-10-16T09:25:00Z POST /token 200 0.194s
27+2026-10-16T09:26:27Z POST /login 200 0.182s
28+2026-10-16T09:27:24Z GET /token 200 0.123s
29+2026-10-16T09:28:45Z GET /login 200 1.969s
30+2026-10-16T09:29:57Z GET /token 200 3.229s
31+2026-10-16T09:30:44Z POST /token 200 0.081s
32+2026-10-16T09:31:02Z GET /logout 200 0.026s
33+2026-10-16T09:32:44Z POST /logout 200 1.788s
34+2026-10-16T09:33:49Z POST /logout 200 0.031s
35+2026-10-16T09:34:49Z POST /login 200 0.053s
36+2026-10-16T09:35:21Z POST /logout 200 2.54s
37+2026-10-16T09:36:40Z POST /logout 500 1.241s
38+2026-10-16T09:37:06Z POST /token 200 0.272s
39+2026-10-16T09:38:51Z POST /login 200 2.024s
40+2026-10-16T09:39:17Z GET /login 200 0.103s
added demos/declared-tools/logs/billing.log +40 -0
new file mode 100644
@@ -0,0 +1,40 @@
1+2026-10-16T09:00:33Z GET /api/payments 200 0.131s
2+2026-10-16T09:01:38Z GET /api/payments 200 0.212s
3+2026-10-16T09:02:10Z GET /api/payments 200 0.135s
4+2026-10-16T09:03:28Z GET /api/invoices 200 0.251s
5+2026-10-16T09:04:29Z GET /api/payments 200 0.069s
6+2026-10-16T09:05:04Z POST /api/invoices 200 0.218s
7+2026-10-16T09:06:45Z GET /api/invoices 200 0.127s
8+2026-10-16T09:07:21Z GET /api/payments 200 0.049s
9+2026-10-16T09:08:36Z POST /api/refunds 200 0.122s
10+2026-10-16T09:09:43Z POST /api/refunds 200 0.195s
11+2026-10-16T09:10:02Z POST /api/payments 200 0.231s
12+2026-10-16T09:11:02Z GET /api/payments 200 3.302s
13+2026-10-16T09:12:05Z GET /api/refunds 200 0.295s
14+2026-10-16T09:13:59Z GET /api/refunds 200 0.107s
15+2026-10-16T09:14:02Z GET /api/refunds 200 0.025s
16+2026-10-16T09:15:39Z POST /api/invoices 500 0.298s
17+2026-10-16T09:16:12Z POST /api/refunds 200 0.268s
18+2026-10-16T09:17:44Z POST /api/refunds 200 0.021s
19+2026-10-16T09:18:36Z POST /api/invoices 200 0.233s
20+2026-10-16T09:19:20Z POST /api/payments 200 0.212s
21+2026-10-16T09:20:55Z POST /api/refunds 200 0.123s
22+2026-10-16T09:21:44Z POST /api/payments 200 0.13s
23+2026-10-16T09:22:56Z POST /api/invoices 500 0.111s
24+2026-10-16T09:23:06Z POST /api/payments 200 0.025s
25+2026-10-16T09:24:08Z POST /api/refunds 200 0.209s
26+2026-10-16T09:25:10Z POST /api/refunds 200 0.088s
27+2026-10-16T09:26:04Z GET /api/payments 200 0.222s
28+2026-10-16T09:27:19Z GET /api/payments 200 0.179s
29+2026-10-16T09:28:33Z GET /api/invoices 200 0.128s
30+2026-10-16T09:29:52Z GET /api/payments 200 0.298s
31+2026-10-16T09:30:28Z POST /api/payments 200 0.035s
32+2026-10-16T09:31:18Z POST /api/refunds 200 0.054s
33+2026-10-16T09:32:11Z POST /api/refunds 200 0.269s
34+2026-10-16T09:33:05Z POST /api/refunds 200 0.149s
35+2026-10-16T09:34:01Z POST /api/invoices 200 0.08s
36+2026-10-16T09:35:28Z POST /api/refunds 200 0.241s
37+2026-10-16T09:36:44Z POST /api/invoices 200 0.102s
38+2026-10-16T09:37:30Z POST /api/payments 500 0.11s
39+2026-10-16T09:38:32Z GET /api/refunds 500 0.024s
40+2026-10-16T09:39:19Z POST /api/invoices 200 0.205s
new file mode 100644
@@ -0,0 +1,40 @@
1+2026-10-16T09:00:33Z GET /api/payments 200 0.131s
2+2026-10-16T09:01:38Z GET /api/payments 200 0.212s
3+2026-10-16T09:02:10Z GET /api/payments 200 0.135s
4+2026-10-16T09:03:28Z GET /api/invoices 200 0.251s
5+2026-10-16T09:04:29Z GET /api/payments 200 0.069s
6+2026-10-16T09:05:04Z POST /api/invoices 200 0.218s
7+2026-10-16T09:06:45Z GET /api/invoices 200 0.127s
8+2026-10-16T09:07:21Z GET /api/payments 200 0.049s
9+2026-10-16T09:08:36Z POST /api/refunds 200 0.122s
10+2026-10-16T09:09:43Z POST /api/refunds 200 0.195s
11+2026-10-16T09:10:02Z POST /api/payments 200 0.231s
12+2026-10-16T09:11:02Z GET /api/payments 200 3.302s
13+2026-10-16T09:12:05Z GET /api/refunds 200 0.295s
14+2026-10-16T09:13:59Z GET /api/refunds 200 0.107s
15+2026-10-16T09:14:02Z GET /api/refunds 200 0.025s
16+2026-10-16T09:15:39Z POST /api/invoices 500 0.298s
17+2026-10-16T09:16:12Z POST /api/refunds 200 0.268s
18+2026-10-16T09:17:44Z POST /api/refunds 200 0.021s
19+2026-10-16T09:18:36Z POST /api/invoices 200 0.233s
20+2026-10-16T09:19:20Z POST /api/payments 200 0.212s
21+2026-10-16T09:20:55Z POST /api/refunds 200 0.123s
22+2026-10-16T09:21:44Z POST /api/payments 200 0.13s
23+2026-10-16T09:22:56Z POST /api/invoices 500 0.111s
24+2026-10-16T09:23:06Z POST /api/payments 200 0.025s
25+2026-10-16T09:24:08Z POST /api/refunds 200 0.209s
26+2026-10-16T09:25:10Z POST /api/refunds 200 0.088s
27+2026-10-16T09:26:04Z GET /api/payments 200 0.222s
28+2026-10-16T09:27:19Z GET /api/payments 200 0.179s
29+2026-10-16T09:28:33Z GET /api/invoices 200 0.128s
30+2026-10-16T09:29:52Z GET /api/payments 200 0.298s
31+2026-10-16T09:30:28Z POST /api/payments 200 0.035s
32+2026-10-16T09:31:18Z POST /api/refunds 200 0.054s
33+2026-10-16T09:32:11Z POST /api/refunds 200 0.269s
34+2026-10-16T09:33:05Z POST /api/refunds 200 0.149s
35+2026-10-16T09:34:01Z POST /api/invoices 200 0.08s
36+2026-10-16T09:35:28Z POST /api/refunds 200 0.241s
37+2026-10-16T09:36:44Z POST /api/invoices 200 0.102s
38+2026-10-16T09:37:30Z POST /api/payments 500 0.11s
39+2026-10-16T09:38:32Z GET /api/refunds 500 0.024s
40+2026-10-16T09:39:19Z POST /api/invoices 200 0.205s
added demos/declared-tools/logs/orders.log +40 -0
new file mode 100644
@@ -0,0 +1,40 @@
1+2026-10-16T09:00:19Z POST /api/cart 200 0.127s
2+2026-10-16T09:01:49Z POST /api/orders 200 0.262s
3+2026-10-16T09:02:57Z POST /api/cart 200 0.16s
4+2026-10-16T09:03:13Z GET /api/orders 500 0.255s
5+2026-10-16T09:04:23Z POST /api/orders 200 0.116s
6+2026-10-16T09:05:10Z GET /api/cart 200 0.162s
7+2026-10-16T09:06:13Z POST /api/orders 200 0.156s
8+2026-10-16T09:07:55Z GET /api/checkout 200 0.269s
9+2026-10-16T09:08:05Z POST /api/orders 200 3.086s
10+2026-10-16T09:09:47Z GET /api/checkout 200 3.45s
11+2026-10-16T09:10:41Z POST /api/orders 500 0.251s
12+2026-10-16T09:11:20Z POST /api/cart 200 0.274s
13+2026-10-16T09:12:10Z POST /api/cart 200 0.225s
14+2026-10-16T09:13:27Z POST /api/cart 200 0.11s
15+2026-10-16T09:14:27Z GET /api/cart 500 0.107s
16+2026-10-16T09:15:58Z GET /api/checkout 200 0.215s
17+2026-10-16T09:16:15Z POST /api/orders 200 0.193s
18+2026-10-16T09:17:53Z GET /api/cart 200 0.103s
19+2026-10-16T09:18:01Z GET /api/checkout 200 0.121s
20+2026-10-16T09:19:10Z GET /api/orders 200 0.218s
21+2026-10-16T09:20:21Z GET /api/orders 200 0.051s
22+2026-10-16T09:21:32Z GET /api/checkout 200 0.028s
23+2026-10-16T09:22:51Z GET /api/cart 500 0.207s
24+2026-10-16T09:23:30Z GET /api/cart 200 0.195s
25+2026-10-16T09:24:47Z GET /api/cart 200 0.171s
26+2026-10-16T09:25:50Z POST /api/orders 200 0.027s
27+2026-10-16T09:26:06Z GET /api/cart 500 0.245s
28+2026-10-16T09:27:57Z POST /api/checkout 200 0.277s
29+2026-10-16T09:28:26Z GET /api/checkout 200 0.12s
30+2026-10-16T09:29:44Z GET /api/checkout 200 0.135s
31+2026-10-16T09:30:45Z GET /api/checkout 200 0.295s
32+2026-10-16T09:31:17Z GET /api/checkout 200 1.776s
33+2026-10-16T09:32:07Z POST /api/checkout 200 0.288s
34+2026-10-16T09:33:00Z POST /api/checkout 200 0.112s
35+2026-10-16T09:34:37Z POST /api/orders 200 0.051s
36+2026-10-16T09:35:05Z POST /api/cart 500 0.123s
37+2026-10-16T09:36:36Z GET /api/cart 200 0.152s
38+2026-10-16T09:37:39Z POST /api/orders 200 2.419s
39+2026-10-16T09:38:56Z POST /api/orders 200 0.257s
40+2026-10-16T09:39:49Z POST /api/checkout 200 2.05s
new file mode 100644
@@ -0,0 +1,40 @@
1+2026-10-16T09:00:19Z POST /api/cart 200 0.127s
2+2026-10-16T09:01:49Z POST /api/orders 200 0.262s
3+2026-10-16T09:02:57Z POST /api/cart 200 0.16s
4+2026-10-16T09:03:13Z GET /api/orders 500 0.255s
5+2026-10-16T09:04:23Z POST /api/orders 200 0.116s
6+2026-10-16T09:05:10Z GET /api/cart 200 0.162s
7+2026-10-16T09:06:13Z POST /api/orders 200 0.156s
8+2026-10-16T09:07:55Z GET /api/checkout 200 0.269s
9+2026-10-16T09:08:05Z POST /api/orders 200 3.086s
10+2026-10-16T09:09:47Z GET /api/checkout 200 3.45s
11+2026-10-16T09:10:41Z POST /api/orders 500 0.251s
12+2026-10-16T09:11:20Z POST /api/cart 200 0.274s
13+2026-10-16T09:12:10Z POST /api/cart 200 0.225s
14+2026-10-16T09:13:27Z POST /api/cart 200 0.11s
15+2026-10-16T09:14:27Z GET /api/cart 500 0.107s
16+2026-10-16T09:15:58Z GET /api/checkout 200 0.215s
17+2026-10-16T09:16:15Z POST /api/orders 200 0.193s
18+2026-10-16T09:17:53Z GET /api/cart 200 0.103s
19+2026-10-16T09:18:01Z GET /api/checkout 200 0.121s
20+2026-10-16T09:19:10Z GET /api/orders 200 0.218s
21+2026-10-16T09:20:21Z GET /api/orders 200 0.051s
22+2026-10-16T09:21:32Z GET /api/checkout 200 0.028s
23+2026-10-16T09:22:51Z GET /api/cart 500 0.207s
24+2026-10-16T09:23:30Z GET /api/cart 200 0.195s
25+2026-10-16T09:24:47Z GET /api/cart 200 0.171s
26+2026-10-16T09:25:50Z POST /api/orders 200 0.027s
27+2026-10-16T09:26:06Z GET /api/cart 500 0.245s
28+2026-10-16T09:27:57Z POST /api/checkout 200 0.277s
29+2026-10-16T09:28:26Z GET /api/checkout 200 0.12s
30+2026-10-16T09:29:44Z GET /api/checkout 200 0.135s
31+2026-10-16T09:30:45Z GET /api/checkout 200 0.295s
32+2026-10-16T09:31:17Z GET /api/checkout 200 1.776s
33+2026-10-16T09:32:07Z POST /api/checkout 200 0.288s
34+2026-10-16T09:33:00Z POST /api/checkout 200 0.112s
35+2026-10-16T09:34:37Z POST /api/orders 200 0.051s
36+2026-10-16T09:35:05Z POST /api/cart 500 0.123s
37+2026-10-16T09:36:36Z GET /api/cart 200 0.152s
38+2026-10-16T09:37:39Z POST /api/orders 200 2.419s
39+2026-10-16T09:38:56Z POST /api/orders 200 0.257s
40+2026-10-16T09:39:49Z POST /api/checkout 200 2.05s
added demos/declared-tools/scripts/bump.sh +15 -0
new file mode 100755
@@ -0,0 +1,15 @@
1+#!/bin/sh
2+# bump.sh <major|minor|patch> — raises the version in VERSION.
3+set -eu
4+cd "$(dirname "$0")/.."
5+old=$(cat VERSION)
6+IFS=. read -r major minor patch <<EOV
7+$old
8+EOV
9+case $1 in
10+ major) major=$((major + 1)); minor=0; patch=0 ;;
11+ minor) minor=$((minor + 1)); patch=0 ;;
12+ patch) patch=$((patch + 1)) ;;
13+esac
14+echo "$major.$minor.$patch" > VERSION
15+echo "version $old -> $major.$minor.$patch"
new file mode 100755
@@ -0,0 +1,15 @@
1+#!/bin/sh
2+# bump.sh <major|minor|patch> — raises the version in VERSION.
3+set -eu
4+cd "$(dirname "$0")/.."
5+old=$(cat VERSION)
6+IFS=. read -r major minor patch <<EOV
7+$old
8+EOV
9+case $1 in
10+ major) major=$((major + 1)); minor=0; patch=0 ;;
11+ minor) minor=$((minor + 1)); patch=0 ;;
12+ patch) patch=$((patch + 1)) ;;
13+esac
14+echo "$major.$minor.$patch" > VERSION
15+echo "version $old -> $major.$minor.$patch"
added demos/declared-tools/scripts/logs.sh +5 -0
new file mode 100755
@@ -0,0 +1,5 @@
1+#!/bin/sh
2+# logs.sh <service> <lines> — the last lines of a service's log.
3+set -eu
4+cd "$(dirname "$0")/.."
5+tail -n "$2" "logs/$1.log"
new file mode 100755
@@ -0,0 +1,5 @@
1+#!/bin/sh
2+# logs.sh <service> <lines> — the last lines of a service's log.
3+set -eu
4+cd "$(dirname "$0")/.."
5+tail -n "$2" "logs/$1.log"
added demos/declared-tools/scripts/release.sh +16 -0
new file mode 100755
@@ -0,0 +1,16 @@
1+#!/bin/sh
2+# release.sh <version> <channel> <dry-run: true|false>
3+# Records a release in releases.log — or only says what it would do.
4+set -eu
5+cd "$(dirname "$0")/.."
6+version=$1 channel=$2 dry_run=$3
7+case $version in
8+ [0-9]*.[0-9]*.[0-9]*) ;;
9+ *) echo "error: \"$version\" is not a version like 1.4.2" >&2; exit 2 ;;
10+esac
11+if [ "$dry_run" = true ]; then
12+ echo "dry run: would release $version on $channel (nothing recorded)"
13+ exit 0
14+fi
15+echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) $version $channel" >> releases.log
16+echo "released $version on $channel (recorded in releases.log)"
new file mode 100755
@@ -0,0 +1,16 @@
1+#!/bin/sh
2+# release.sh <version> <channel> <dry-run: true|false>
3+# Records a release in releases.log — or only says what it would do.
4+set -eu
5+cd "$(dirname "$0")/.."
6+version=$1 channel=$2 dry_run=$3
7+case $version in
8+ [0-9]*.[0-9]*.[0-9]*) ;;
9+ *) echo "error: \"$version\" is not a version like 1.4.2" >&2; exit 2 ;;
10+esac
11+if [ "$dry_run" = true ]; then
12+ echo "dry run: would release $version on $channel (nothing recorded)"
13+ exit 0
14+fi
15+echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) $version $channel" >> releases.log
16+echo "released $version on $channel (recorded in releases.log)"
added demos/declared-tools/scripts/slow.sh +10 -0
new file mode 100755
@@ -0,0 +1,10 @@
1+#!/bin/sh
2+# slow.sh <threshold-seconds> [service] — requests slower than the threshold.
3+# Log lines: <time> <method> <path> <status> <duration>s
4+set -eu
5+cd "$(dirname "$0")/.."
6+threshold=$1
7+files=logs/*.log
8+[ $# -ge 2 ] && files="logs/$2.log"
9+# shellcheck disable=SC2086 # files is a glob or one path, split on purpose
10+awk -v t="$threshold" '{ d = $5; sub(/s$/, "", d); if (d + 0 > t + 0) print FILENAME ": " $0 }' $files | sed 's|^logs/||'
new file mode 100755
@@ -0,0 +1,10 @@
1+#!/bin/sh
2+# slow.sh <threshold-seconds> [service] — requests slower than the threshold.
3+# Log lines: <time> <method> <path> <status> <duration>s
4+set -eu
5+cd "$(dirname "$0")/.."
6+threshold=$1
7+files=logs/*.log
8+[ $# -ge 2 ] && files="logs/$2.log"
9+# shellcheck disable=SC2086 # files is a glob or one path, split on purpose
10+awk -v t="$threshold" '{ d = $5; sub(/s$/, "", d); if (d + 0 > t + 0) print FILENAME ": " $0 }' $files | sed 's|^logs/||'
modified docs/diagrams/packages.drawio +1 -1
@@ -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="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell>
1+<mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content; declared toolsbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;lt;/fontbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell>
22 <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>
33 <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="&lt;b&gt;internal/structured&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;structuredOutput: Go declarations → JSON Schema (go/parser)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="&lt;b&gt;internal/agentsmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the project's AGENTS.md, appended to the system prompt per conversation&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="&lt;b&gt;internal/elicit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ask_user's questions and answers: normalize, line answers, elicitation form schema&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="&lt;b&gt;internal/hooks&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><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="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell>1+<mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content; declared toolsbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;lt;/fontbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell>
2 <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell>2 <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell>
3 <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="&lt;b&gt;internal/structured&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;structuredOutput: Go declarations → JSON Schema (go/parser)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="&lt;b&gt;internal/agentsmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the project's AGENTS.md, appended to the system prompt per conversation&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="&lt;b&gt;internal/elicit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ask_user's questions and answers: normalize, line answers, elicitation form schema&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="&lt;b&gt;internal/hooks&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile>3 <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="&lt;b&gt;internal/structured&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;structuredOutput: Go declarations → JSON Schema (go/parser)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="&lt;b&gt;internal/agentsmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the project's AGENTS.md, appended to the system prompt per conversation&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="&lt;b&gt;internal/elicit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ask_user's questions and answers: normalize, line answers, elicitation form schema&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="&lt;b&gt;internal/hooks&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><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 +3 -1
@@ -25,6 +25,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
2525 - [Let the agent ask you questions](how-to/let-the-agent-ask-you.md) — `ask_user`: answer in the terminal, the window or an editor, turn it off, decide for the agents yours calls (`askUserTool`, `mmConfigs.<name>.askUser`, `AGENT_ASK_USER`)
2626 - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the `mini-me` kit from [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), the `go-toolchain` and `cli-tools` mixins under `sbx/`, window mode and editor
2727 - [Add a skill](how-to/add-a-skill.md)
28+- [Declare a custom tool](how-to/declare-a-custom-tool.md)
2829 - [Measure whether the model picks the right skill](how-to/measure-skill-routing.md) — the routing eval: run it, read its table, use it as a before-and-after
2930 - [Keep a long session inside the context window](how-to/manage-the-context-window.md)
3031 - [Start a new session without restarting](how-to/start-a-new-session.md)
@@ -48,7 +49,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
4849 - [Command line](reference/cli.md) — flags, environment variables, start-up banner
4950 - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys
5051 - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule
51-- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`
52+- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, declared tools
5253 - [Skill file format](reference/skill-format.md)
5354 - [Hooks](reference/hooks.md) — the six events, the JSON a hook reads and writes, `matcher`, `timeout`, `onError`, validation errors
5455 - [Agent Client Protocol surface](reference/acp.md)
@@ -66,6 +67,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
6667 - [Persistent sessions](explanation/persistent-sessions.md) — files per project, why a start is a new session, `session/load` versus docker agent's `session/resume`
6768 - [The `lsp` tool](explanation/lsp-tool.md) — one tool with an `action` field, a `symbol` field instead of columns, files synced from disk, rename gated by permission
6869 - [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
70+- [Declared tools](explanation/declared-tools.md) — why typed tools beside skills, what was measured, no shell, invalid calls answered in text
6971 - [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
7072 - [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`)
7173 - [Project instructions (`AGENTS.md`)](explanation/project-instructions.md) — why appended to the system prompt, why per conversation, why a warning rather than a cut, who does not read it
@@ -25,6 +25,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
25 - [Let the agent ask you questions](how-to/let-the-agent-ask-you.md) — `ask_user`: answer in the terminal, the window or an editor, turn it off, decide for the agents yours calls (`askUserTool`, `mmConfigs.<name>.askUser`, `AGENT_ASK_USER`)25 - [Let the agent ask you questions](how-to/let-the-agent-ask-you.md) — `ask_user`: answer in the terminal, the window or an editor, turn it off, decide for the agents yours calls (`askUserTool`, `mmConfigs.<name>.askUser`, `AGENT_ASK_USER`)
26 - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the `mini-me` kit from [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), the `go-toolchain` and `cli-tools` mixins under `sbx/`, window mode and editor26 - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the `mini-me` kit from [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), the `go-toolchain` and `cli-tools` mixins under `sbx/`, window mode and editor
27 - [Add a skill](how-to/add-a-skill.md)27 - [Add a skill](how-to/add-a-skill.md)
28+- [Declare a custom tool](how-to/declare-a-custom-tool.md)
28 - [Measure whether the model picks the right skill](how-to/measure-skill-routing.md) — the routing eval: run it, read its table, use it as a before-and-after29 - [Measure whether the model picks the right skill](how-to/measure-skill-routing.md) — the routing eval: run it, read its table, use it as a before-and-after
29 - [Keep a long session inside the context window](how-to/manage-the-context-window.md)30 - [Keep a long session inside the context window](how-to/manage-the-context-window.md)
30 - [Start a new session without restarting](how-to/start-a-new-session.md)31 - [Start a new session without restarting](how-to/start-a-new-session.md)
@@ -48,7 +49,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
48 - [Command line](reference/cli.md) — flags, environment variables, start-up banner49 - [Command line](reference/cli.md) — flags, environment variables, start-up banner
49 - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys50 - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys
50 - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule51 - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule
51-- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`52+- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, declared tools
52 - [Skill file format](reference/skill-format.md)53 - [Skill file format](reference/skill-format.md)
53 - [Hooks](reference/hooks.md) — the six events, the JSON a hook reads and writes, `matcher`, `timeout`, `onError`, validation errors54 - [Hooks](reference/hooks.md) — the six events, the JSON a hook reads and writes, `matcher`, `timeout`, `onError`, validation errors
54 - [Agent Client Protocol surface](reference/acp.md)55 - [Agent Client Protocol surface](reference/acp.md)
@@ -66,6 +67,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
66 - [Persistent sessions](explanation/persistent-sessions.md) — files per project, why a start is a new session, `session/load` versus docker agent's `session/resume`67 - [Persistent sessions](explanation/persistent-sessions.md) — files per project, why a start is a new session, `session/load` versus docker agent's `session/resume`
67 - [The `lsp` tool](explanation/lsp-tool.md) — one tool with an `action` field, a `symbol` field instead of columns, files synced from disk, rename gated by permission68 - [The `lsp` tool](explanation/lsp-tool.md) — one tool with an `action` field, a `symbol` field instead of columns, files synced from disk, rename gated by permission
68 - [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 limits69 - [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
70+- [Declared tools](explanation/declared-tools.md) — why typed tools beside skills, what was measured, no shell, invalid calls answered in text
69 - [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 alive71 - [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
70 - [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`)72 - [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`)
71 - [Project instructions (`AGENTS.md`)](explanation/project-instructions.md) — why appended to the system prompt, why per conversation, why a warning rather than a cut, who does not read it73 - [Project instructions (`AGENTS.md`)](explanation/project-instructions.md) — why appended to the system prompt, why per conversation, why a warning rather than a cut, who does not read it
added docs/en/explanation/declared-tools.md +54 -0
new file mode 100644
@@ -0,0 +1,54 @@
1+# Declared tools — explanation
2+
3+## What is this about?
4+
5+mm had two ways to let the model run a command of yours before ticket 88: a [skill](../how-to/add-a-skill.md) that says "call your `bash` tool with this command", and an MCP server exposing a tool. Declared tools are a third: a `tools:` entry in the agent file that turns one command into a typed tool, with no server to write. This page explains why it was added beside skills rather than instead of them, and why it is shaped the way it is.
6+
7+## Why skills were not enough
8+
9+A skill runs a command in two steps. The model must first find the skill in the catalogue and call `read_skill`, then compose a `bash` command line from what it read. Each step can fail, and on a small model they do:
10+
11+- **The skill is never found.** The catalogue is routed by the model reading names, descriptions and keywords. Measured on mellum2-12b, a `status` skill ("Report the status of one of the company's services") was never read for "What is the status of the billing service?" — 0 out of 10 runs, in English and in French. The model improvised `curl localhost:8080/health` and `ps aux | grep billing`, and then answered that the service "seems to be running". That is a wrong answer, not just a miss.
12+- **The command is rewritten.** When the skill is skipped but the model finds the script anyway, it guesses the arguments: `./pkgtest.sh greet` instead of `./greet`.
13+- **Consent is all or nothing.** Everything a skill runs goes through `bash`, so "always allow this script" can only be said as "always allow bash".
14+
15+A declared tool removes the routing step: its name, description and parameters are in every request, like `bash`'s.
16+
17+## What was measured
18+
19+Three actions (`release <version> <channel>`, `pkgtest <package>`, `status <service>`), each asked in English and in French, 5 runs each, `mm -p`, on two models. A run succeeds when the script really ran with exactly the expected arguments — checked from a file the script writes, not from the model's answer.
20+
21+| Model | Skill + `bash` | Typed tool through an MCP stand-in | Declared tool |
22+|-------|----------------|-------------------------------------|---------------|
23+| mellum2-12b | 15/30 (4.5 s on average) | 25/30 (2.8 s) | 30/30 (1.3 s) |
24+| qwen3.5-4b | 30/30 (12.7 s) | 30/30 (7.7 s) | 30/30 (5.8 s) |
25+
26+The MCP stand-in was measured first, before any code was written: a small MCP HTTP server exposing the same three tools gave the same comparison without building anything. It decided the question. The declared tools were measured again once built.
27+
28+Two things stand out. First, a typed tool is faster even when the skill works: one model round trip instead of two. Second, the stand-in's five failures on mellum2 — the French release request — came from the model packing every argument into one field: `{version: "1.4.2 1.4.2, channel beta"}`. With the declared tool, the same request succeeded 5 times out of 5, each time in one call. The declared tool differs from the stand-in in several ways at once — no `proj_` name prefix, no "MCP tool from server …" in the description, a closing "Give each parameter as its own field." — and which of them matters was not measured separately.
29+
30+The numbers come from one small fixture, two models and five runs per case. They answer "is it worth building?", not "how much better is it in general?".
31+
32+## Why it is designed this way
33+
34+**No shell.** The command is a list, one argv entry per element, and a value replaces its `{{placeholder}}` inside one entry. A model's value — or a value a prompt injection put in the conversation — cannot add an argument, chain a second command or redirect an output: `1.4.2; rm -rf ~` reaches the script as one odd version string. A template like `command: "./release.sh {{version}}"` run by `bash -c` was rejected for exactly that reason. A command that needs a pipe goes into a script.
35+
36+**The program cannot come from a parameter.** Otherwise a declared tool is `bash` with extra steps.
37+
38+**Invalid calls are answered, not rejected by the schema.** The schema the model reads carries the types, but not `required` nor `enum` as keywords — those go into the descriptions. Genkit validates a call against the declared schema before running the tool, and a call it rejects ends the turn. mm prefers to answer in text, saying what was wrong, what the model sent and what each parameter should be, so the model can try again. The same choice was made for MCP tools. Quoting the call back matters: told only "channel is missing", mellum2 sent the same packed call six times until the [loop detector](../reference/tools.md#loop-detection) stopped the turn.
39+
40+**Lenient coercion, nothing more.** Models write `"3"` for 3 and `"true"` for true often enough that those strings are accepted. Anything that would need a guess (`"yes"`, `"three"`) is refused with the reason.
41+
42+**Consent follows `yolo`.** A declared tool runs something, so with `yolo: false` it asks like `bash`; with `yolo: true` it does not. Asking "even in yolo" was considered and left out: the agent's author wrote the command, which is already a stronger statement than a model composing a `bash` line.
43+
44+**Names are reserved early.** A declared tool cannot take a built-in name even when that tool is turned off, so turning it on later does not break the file. A clash with an MCP tool can only be seen once the server answered, so it is a start-up warning, not an error.
45+
46+## Rejected alternatives
47+
48+- **Only skills.** Cheaper to write and fine for procedures, but measured to fail where it matters on the small models mm targets.
49+- **Only MCP.** Already gives typed tools, but each one needs a server to write, start and keep running, over HTTP only (mm has no MCP stdio transport). Too heavy for "run this script with two arguments".
50+- **A Wasm plugin system** (ticket 46). Sandboxed tools are a different question; a declared tool runs with the user's rights, like `bash`.
51+
52+## How it relates to the rest
53+
54+Declared tools sit beside the built-in tools: the same loop detector, the same output truncation and echo, the same working directory and cancellation as [`bash`](../reference/tools.md#bash), the same permission dialogs (see [yolo and permissions](yolo-and-permissions.md)), and the `preToolCall` / `postToolCall` [hooks](hooks.md). Skills remain the place for know-how and procedures; a skill can also tell the model to call a declared tool.
new file mode 100644
@@ -0,0 +1,54 @@
1+# Declared tools — explanation
2+
3+## What is this about?
4+
5+mm had two ways to let the model run a command of yours before ticket 88: a [skill](../how-to/add-a-skill.md) that says "call your `bash` tool with this command", and an MCP server exposing a tool. Declared tools are a third: a `tools:` entry in the agent file that turns one command into a typed tool, with no server to write. This page explains why it was added beside skills rather than instead of them, and why it is shaped the way it is.
6+
7+## Why skills were not enough
8+
9+A skill runs a command in two steps. The model must first find the skill in the catalogue and call `read_skill`, then compose a `bash` command line from what it read. Each step can fail, and on a small model they do:
10+
11+- **The skill is never found.** The catalogue is routed by the model reading names, descriptions and keywords. Measured on mellum2-12b, a `status` skill ("Report the status of one of the company's services") was never read for "What is the status of the billing service?" — 0 out of 10 runs, in English and in French. The model improvised `curl localhost:8080/health` and `ps aux | grep billing`, and then answered that the service "seems to be running". That is a wrong answer, not just a miss.
12+- **The command is rewritten.** When the skill is skipped but the model finds the script anyway, it guesses the arguments: `./pkgtest.sh greet` instead of `./greet`.
13+- **Consent is all or nothing.** Everything a skill runs goes through `bash`, so "always allow this script" can only be said as "always allow bash".
14+
15+A declared tool removes the routing step: its name, description and parameters are in every request, like `bash`'s.
16+
17+## What was measured
18+
19+Three actions (`release <version> <channel>`, `pkgtest <package>`, `status <service>`), each asked in English and in French, 5 runs each, `mm -p`, on two models. A run succeeds when the script really ran with exactly the expected arguments — checked from a file the script writes, not from the model's answer.
20+
21+| Model | Skill + `bash` | Typed tool through an MCP stand-in | Declared tool |
22+|-------|----------------|-------------------------------------|---------------|
23+| mellum2-12b | 15/30 (4.5 s on average) | 25/30 (2.8 s) | 30/30 (1.3 s) |
24+| qwen3.5-4b | 30/30 (12.7 s) | 30/30 (7.7 s) | 30/30 (5.8 s) |
25+
26+The MCP stand-in was measured first, before any code was written: a small MCP HTTP server exposing the same three tools gave the same comparison without building anything. It decided the question. The declared tools were measured again once built.
27+
28+Two things stand out. First, a typed tool is faster even when the skill works: one model round trip instead of two. Second, the stand-in's five failures on mellum2 — the French release request — came from the model packing every argument into one field: `{version: "1.4.2 1.4.2, channel beta"}`. With the declared tool, the same request succeeded 5 times out of 5, each time in one call. The declared tool differs from the stand-in in several ways at once — no `proj_` name prefix, no "MCP tool from server …" in the description, a closing "Give each parameter as its own field." — and which of them matters was not measured separately.
29+
30+The numbers come from one small fixture, two models and five runs per case. They answer "is it worth building?", not "how much better is it in general?".
31+
32+## Why it is designed this way
33+
34+**No shell.** The command is a list, one argv entry per element, and a value replaces its `{{placeholder}}` inside one entry. A model's value — or a value a prompt injection put in the conversation — cannot add an argument, chain a second command or redirect an output: `1.4.2; rm -rf ~` reaches the script as one odd version string. A template like `command: "./release.sh {{version}}"` run by `bash -c` was rejected for exactly that reason. A command that needs a pipe goes into a script.
35+
36+**The program cannot come from a parameter.** Otherwise a declared tool is `bash` with extra steps.
37+
38+**Invalid calls are answered, not rejected by the schema.** The schema the model reads carries the types, but not `required` nor `enum` as keywords — those go into the descriptions. Genkit validates a call against the declared schema before running the tool, and a call it rejects ends the turn. mm prefers to answer in text, saying what was wrong, what the model sent and what each parameter should be, so the model can try again. The same choice was made for MCP tools. Quoting the call back matters: told only "channel is missing", mellum2 sent the same packed call six times until the [loop detector](../reference/tools.md#loop-detection) stopped the turn.
39+
40+**Lenient coercion, nothing more.** Models write `"3"` for 3 and `"true"` for true often enough that those strings are accepted. Anything that would need a guess (`"yes"`, `"three"`) is refused with the reason.
41+
42+**Consent follows `yolo`.** A declared tool runs something, so with `yolo: false` it asks like `bash`; with `yolo: true` it does not. Asking "even in yolo" was considered and left out: the agent's author wrote the command, which is already a stronger statement than a model composing a `bash` line.
43+
44+**Names are reserved early.** A declared tool cannot take a built-in name even when that tool is turned off, so turning it on later does not break the file. A clash with an MCP tool can only be seen once the server answered, so it is a start-up warning, not an error.
45+
46+## Rejected alternatives
47+
48+- **Only skills.** Cheaper to write and fine for procedures, but measured to fail where it matters on the small models mm targets.
49+- **Only MCP.** Already gives typed tools, but each one needs a server to write, start and keep running, over HTTP only (mm has no MCP stdio transport). Too heavy for "run this script with two arguments".
50+- **A Wasm plugin system** (ticket 46). Sandboxed tools are a different question; a declared tool runs with the user's rights, like `bash`.
51+
52+## How it relates to the rest
53+
54+Declared tools sit beside the built-in tools: the same loop detector, the same output truncation and echo, the same working directory and cancellation as [`bash`](../reference/tools.md#bash), the same permission dialogs (see [yolo and permissions](yolo-and-permissions.md)), and the `preToolCall` / `postToolCall` [hooks](hooks.md). Skills remain the place for know-how and procedures; a skill can also tell the model to call a declared tool.
added docs/en/how-to/declare-a-custom-tool.md +61 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/en/reference/configuration.md +45 -2
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/en/reference/tools.md +21 -0
@@ -248,6 +248,27 @@ The declared schema describes a question's fields in words, without `properties`
248248 | Result | Answered: `The user answered:` then one `- <question> → <answer>` line per question (several options joined by `, `). Declined: `[the user declined to answer: do not ask the same thing again; go on without it or stop]`. Cancelled: `[the question was cancelled before the user answered]`. Nobody to ask: `[the user cannot be asked in this mode: make a reasonable assumption, say which, and go on]`. Anything but an answer ends the call as failed in the window and the editor. |
249249 | Errors | `[ask_user: no question — send questions: [{question, type, options}]]`; `[ask_user: N questions — ask at most 4 at a time]`; `[ask_user: question N has no text]`; `[ask_user: question N is a choice question — give it at least two options]` (or `multi`); `[ask_user: question N has type "x" — use choice, multi, text or confirm]`. |
250250
251+## Declared tools
252+
253+Declared from the `tools:` block of the agent file, one tool per entry (see [configuration](configuration.md#tools-keys)). Each has the entry's `name`; its fields are the entry's `parameters`.
254+
255+| Aspect | Behaviour |
256+|--------|-----------|
257+| Schema sent to the model | One property per parameter with its `type` and a description made of the parameter's `description`, `One of: …` for an `enum`, and `Optional.` or `Optional, default <v>.` for an optional parameter. No `required` and no `enum` keywords. The tool's description ends with `Required parameters: <names>. Give each parameter as its own field.` |
258+| Values | Coerced to the parameter's type. `number` and `integer` also accept a numeric string (`"3"`), `boolean` also accepts `"true"`/`"false"` (any case), `string` also accepts a number or a boolean. Rendered as text: integers without a decimal point, numbers without an exponent, booleans as `true`/`false`. |
259+| Optional parameter left out | Its `default` when there is one. Otherwise a command entry that is only its placeholder (`"{{p}}"`) is removed; an entry that contains it with other text gets an empty string in its place. |
260+| Invalid call | Nothing runs and nothing is asked. The output is `[<name>: invalid call, nothing was run — <faults>. You sent: <the call's input as JSON>. Expected: <each parameter with its type and description>. Call <name> again with each parameter as its own field.]`. Faults, all listed: `<p> is missing`, `<p>: "<v>" is not an integer` (or a number, a boolean, a string), `<p>: <v> is not one of <values>`, `"<key>" is not a parameter of this tool`. |
261+| Execution | The command's argv, without a shell: a value never adds an argument, a pipe or a redirection. |
262+| Working directory | The process directory in terminal mode; the session `cwd` in ACP mode and the window. |
263+| Timeout | The entry's `timeout`, else `bashTimeout`; the output then ends with `[error: <timeout> timeout exceeded]`. |
264+| Program not found | `[error: <program> cannot be started: <reason>]`. |
265+| Cancellation, output, non-zero exit | As `bash`. |
266+| Permission | Requested when `yolo: false`; a refusal returns `[<name> rejected by the user]`. |
267+| Terminal display | `🧰 <name>: <argv>` (or `🧰 <name>: <input>` for an invalid call), then the output like `bash`'s, the refusal text included. |
268+| ACP and window | Tool call of kind `execute`, title `<name>: <argv>`. |
269+| Recap | Not counted in `⚙ N command(s)`. |
270+| Hooks | `preToolCall` and `postToolCall` apply, with the tool's name as `matcher`. |
271+
251272 ## Loop detection
252273
253274 Every tool records `(tool, input, output)` in the loop detector after each call.
@@ -248,6 +248,27 @@ The declared schema describes a question's fields in words, without `properties`
248 | Result | Answered: `The user answered:` then one `- <question> → <answer>` line per question (several options joined by `, `). Declined: `[the user declined to answer: do not ask the same thing again; go on without it or stop]`. Cancelled: `[the question was cancelled before the user answered]`. Nobody to ask: `[the user cannot be asked in this mode: make a reasonable assumption, say which, and go on]`. Anything but an answer ends the call as failed in the window and the editor. |248 | Result | Answered: `The user answered:` then one `- <question> → <answer>` line per question (several options joined by `, `). Declined: `[the user declined to answer: do not ask the same thing again; go on without it or stop]`. Cancelled: `[the question was cancelled before the user answered]`. Nobody to ask: `[the user cannot be asked in this mode: make a reasonable assumption, say which, and go on]`. Anything but an answer ends the call as failed in the window and the editor. |
249 | Errors | `[ask_user: no question — send questions: [{question, type, options}]]`; `[ask_user: N questions — ask at most 4 at a time]`; `[ask_user: question N has no text]`; `[ask_user: question N is a choice question — give it at least two options]` (or `multi`); `[ask_user: question N has type "x" — use choice, multi, text or confirm]`. |249 | Errors | `[ask_user: no question — send questions: [{question, type, options}]]`; `[ask_user: N questions — ask at most 4 at a time]`; `[ask_user: question N has no text]`; `[ask_user: question N is a choice question — give it at least two options]` (or `multi`); `[ask_user: question N has type "x" — use choice, multi, text or confirm]`. |
250 250
251+## Declared tools
252+
253+Declared from the `tools:` block of the agent file, one tool per entry (see [configuration](configuration.md#tools-keys)). Each has the entry's `name`; its fields are the entry's `parameters`.
254+
255+| Aspect | Behaviour |
256+|--------|-----------|
257+| Schema sent to the model | One property per parameter with its `type` and a description made of the parameter's `description`, `One of: …` for an `enum`, and `Optional.` or `Optional, default <v>.` for an optional parameter. No `required` and no `enum` keywords. The tool's description ends with `Required parameters: <names>. Give each parameter as its own field.` |
258+| Values | Coerced to the parameter's type. `number` and `integer` also accept a numeric string (`"3"`), `boolean` also accepts `"true"`/`"false"` (any case), `string` also accepts a number or a boolean. Rendered as text: integers without a decimal point, numbers without an exponent, booleans as `true`/`false`. |
259+| Optional parameter left out | Its `default` when there is one. Otherwise a command entry that is only its placeholder (`"{{p}}"`) is removed; an entry that contains it with other text gets an empty string in its place. |
260+| Invalid call | Nothing runs and nothing is asked. The output is `[<name>: invalid call, nothing was run — <faults>. You sent: <the call's input as JSON>. Expected: <each parameter with its type and description>. Call <name> again with each parameter as its own field.]`. Faults, all listed: `<p> is missing`, `<p>: "<v>" is not an integer` (or a number, a boolean, a string), `<p>: <v> is not one of <values>`, `"<key>" is not a parameter of this tool`. |
261+| Execution | The command's argv, without a shell: a value never adds an argument, a pipe or a redirection. |
262+| Working directory | The process directory in terminal mode; the session `cwd` in ACP mode and the window. |
263+| Timeout | The entry's `timeout`, else `bashTimeout`; the output then ends with `[error: <timeout> timeout exceeded]`. |
264+| Program not found | `[error: <program> cannot be started: <reason>]`. |
265+| Cancellation, output, non-zero exit | As `bash`. |
266+| Permission | Requested when `yolo: false`; a refusal returns `[<name> rejected by the user]`. |
267+| Terminal display | `🧰 <name>: <argv>` (or `🧰 <name>: <input>` for an invalid call), then the output like `bash`'s, the refusal text included. |
268+| ACP and window | Tool call of kind `execute`, title `<name>: <argv>`. |
269+| Recap | Not counted in `⚙ N command(s)`. |
270+| Hooks | `preToolCall` and `postToolCall` apply, with the tool's name as `matcher`. |
271+
251 ## Loop detection272 ## Loop detection
252 273
253 Every tool records `(tool, input, output)` in the loop detector after each call.274 Every tool records `(tool, input, output)` in the loop detector after each call.
modified docs/fr/README.md +3 -1
Large diff collapsed to keep the page fast — load it when you need it.
added docs/fr/explanation/declared-tools.md +54 -0
Large diff collapsed to keep the page fast — load it when you need it.
added docs/fr/how-to/declare-a-custom-tool.md +61 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/reference/configuration.md +45 -2
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/reference/tools.md +21 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/config/config.go +8 -0
@@ -215,6 +215,11 @@ type Config struct {
215215 // turn (internal/hooks, ticket 36). They read one JSON object on stdin
216216 // and may answer one on stdout. None by default.
217217 Hooks HooksConfig `yaml:"hooks"`
218+
219+ // Tools: commands declared as typed tools (ticket 88) — each one a name,
220+ // a description, its parameters and an argv command run without a shell.
221+ // None by default. See tools.go.
222+ Tools []DeclaredTool `yaml:"tools"`
218223 }
219224
220225 // YoloEnv names the variable that overrides `yolo` for one run ("true",
@@ -804,5 +809,8 @@ func (c Config) validate() error {
804809 if err := c.Hooks.validate(); err != nil {
805810 return err
806811 }
812+ if err := c.validateTools(); err != nil {
813+ return err
814+ }
807815 return c.LSP.validate()
808816 }
@@ -215,6 +215,11 @@ type Config struct {
215 // turn (internal/hooks, ticket 36). They read one JSON object on stdin215 // turn (internal/hooks, ticket 36). They read one JSON object on stdin
216 // and may answer one on stdout. None by default.216 // and may answer one on stdout. None by default.
217 Hooks HooksConfig `yaml:"hooks"`217 Hooks HooksConfig `yaml:"hooks"`
218+
219+ // Tools: commands declared as typed tools (ticket 88) — each one a name,
220+ // a description, its parameters and an argv command run without a shell.
221+ // None by default. See tools.go.
222+ Tools []DeclaredTool `yaml:"tools"`
218 }223 }
219 224
220 // YoloEnv names the variable that overrides `yolo` for one run ("true",225 // YoloEnv names the variable that overrides `yolo` for one run ("true",
@@ -804,5 +809,8 @@ func (c Config) validate() error {
804 if err := c.Hooks.validate(); err != nil {809 if err := c.Hooks.validate(); err != nil {
805 return err810 return err
806 }811 }
812+ if err := c.validateTools(); err != nil {
813+ return err
814+ }
807 return c.LSP.validate()815 return c.LSP.validate()
808 }816 }
added internal/config/tools.go +349 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/config/tools_test.go +144 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/mcp/tool_wrapper.go +1 -10
Large diff collapsed to keep the page fast — load it when you need it.
added internal/tools/declared.go +283 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/tools/declared_test.go +200 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/tools/tools.go +12 -2
Large diff collapsed to keep the page fast — load it when you need it.
modified main.go +19 -1
Large diff collapsed to keep the page fast — load it when you need it.