declared tools #31
Merged@k33g wants to merge
feature/declared-tools into mainMerged as
23419402393a.40 files changed
modified
.tickets/issues/0031-how-to-use-mini-me-with-gemini.yaml +3 -2 | @@ -6,8 +6,9 @@ author: | ||
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | 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 | |
| 11 | 12 | epic: Providers (LLM Engine) |
| 12 | 13 | body: | |
| 13 | 14 | ## Analysis (Claude, 2026-09-21 — not implemented) |
| @@ -6,8 +6,9 @@ author: | |||
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-09-21T04:01:15.166Z | 8 | createdAt: 2026-09-21T04:01:15.166Z |
| 9 | -updatedAt: 2026-10-06T04:01:18.848Z | 9 | +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: | ||
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-09-27T03:45:48.121Z |
| 9 | -updatedAt: 2026-10-12T09:53:49.053Z | |
| 9 | +updatedAt: 2026-10-16T11:48:27.806Z | |
| 10 | 10 | labels: [] |
| 11 | -milestone: v0.5.0 | |
| 11 | +milestone: v0.6.0 | |
| 12 | 12 | epic: Agent Memory |
| 13 | 13 | body: "" |
| 14 | 14 | tasks: [] |
| @@ -6,9 +6,9 @@ author: | |||
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-09-27T03:45:48.121Z | 8 | createdAt: 2026-09-27T03:45:48.121Z |
| 9 | -updatedAt: 2026-10-12T09:53:49.053Z | 9 | +updatedAt: 2026-10-16T11:48:27.806Z |
| 10 | labels: [] | 10 | labels: [] |
| 11 | -milestone: v0.5.0 | 11 | +milestone: v0.6.0 |
| 12 | epic: Agent Memory | 12 | 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: | ||
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-09-28T18:44:44.821Z |
| 9 | -updatedAt: 2026-10-12T09:54:01.192Z | |
| 9 | +updatedAt: 2026-10-16T11:48:41.005Z | |
| 10 | 10 | labels: [] |
| 11 | -milestone: v0.5.0 | |
| 11 | +milestone: v0.6.0 | |
| 12 | 12 | epic: ToDo |
| 13 | 13 | body: "" |
| 14 | 14 | tasks: [] |
| @@ -6,9 +6,9 @@ author: | |||
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-09-28T18:44:44.821Z | 8 | createdAt: 2026-09-28T18:44:44.821Z |
| 9 | -updatedAt: 2026-10-12T09:54:01.192Z | 9 | +updatedAt: 2026-10-16T11:48:41.005Z |
| 10 | labels: [] | 10 | labels: [] |
| 11 | -milestone: v0.5.0 | 11 | +milestone: v0.6.0 |
| 12 | epic: ToDo | 12 | 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 @@ | ||
| 1 | 1 | # Managed by IssueSpec. Hand edits are welcome; keep the schema valid. |
| 2 | 2 | id: 55 |
| 3 | 3 | title: How to query another model and force the JSON output (then use the result) |
| 4 | -state: open | |
| 4 | +state: closed | |
| 5 | 5 | author: |
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-09-30T06:37:48.693Z |
| 9 | -updatedAt: 2026-10-06T13:14:33.928Z | |
| 9 | +updatedAt: 2026-10-16T11:36:43.967Z | |
| 10 | 10 | labels: [] |
| 11 | +milestone: v0.3.0 | |
| 11 | 12 | epic: Models |
| 12 | 13 | related: |
| 13 | 14 | - 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: 55 | 2 | 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: open | 4 | +state: closed |
| 5 | author: | 5 | author: |
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-09-30T06:37:48.693Z | 8 | createdAt: 2026-09-30T06:37:48.693Z |
| 9 | -updatedAt: 2026-10-06T13:14:33.928Z | 9 | +updatedAt: 2026-10-16T11:36:43.967Z |
| 10 | labels: [] | 10 | labels: [] |
| 11 | +milestone: v0.3.0 | ||
| 11 | epic: Models | 12 | epic: Models |
| 12 | related: | 13 | related: |
| 13 | - 54 | 14 | - 54 |
modified
.tickets/issues/0087-hook-for-ask-agents.yaml +2 -2 | @@ -1,12 +1,12 @@ | ||
| 1 | 1 | # Managed by IssueSpec. Hand edits are welcome; keep the schema valid. |
| 2 | 2 | id: 87 |
| 3 | 3 | title: Hook for ask agents |
| 4 | -state: open | |
| 4 | +state: closed | |
| 5 | 5 | author: |
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-10-15T21:55:43.205Z |
| 9 | -updatedAt: 2026-10-15T23:50:00.000Z | |
| 9 | +updatedAt: 2026-10-16T11:35:47.159Z | |
| 10 | 10 | labels: [] |
| 11 | 11 | milestone: v0.5.0 |
| 12 | 12 | 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: 87 | 2 | id: 87 |
| 3 | title: Hook for ask agents | 3 | title: Hook for ask agents |
| 4 | -state: open | 4 | +state: closed |
| 5 | author: | 5 | author: |
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-10-15T21:55:43.205Z | 8 | createdAt: 2026-10-15T21:55:43.205Z |
| 9 | -updatedAt: 2026-10-15T23:50:00.000Z | 9 | +updatedAt: 2026-10-16T11:35:47.159Z |
| 10 | labels: [] | 10 | labels: [] |
| 11 | milestone: v0.5.0 | 11 | 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: | ||
| 19 | 19 | state: closed |
| 20 | 20 | dueOn: 2026-10-16 |
| 21 | 21 | - title: v0.5.0 |
| 22 | + state: closed | |
| 23 | + dueOn: 2026-10-28 | |
| 24 | + - title: v0.6.0 | |
| 22 | 25 | state: open |
| 23 | 26 | dueOn: 2026-10-28 |
| @@ -19,5 +19,8 @@ milestones: | |||
| 19 | state: closed | 19 | state: closed |
| 20 | dueOn: 2026-10-16 | 20 | dueOn: 2026-10-16 |
| 21 | - title: v0.5.0 | 21 | - title: v0.5.0 |
| 22 | + state: closed | ||
| 23 | + dueOn: 2026-10-28 | ||
| 24 | + - title: v0.6.0 | ||
| 22 | state: open | 25 | state: open |
| 23 | dueOn: 2026-10-28 | 26 | 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 | ||
| 145 | 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 | 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 | 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 | 150 | ## Learn more |
| 150 | 151 | |
| @@ -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 more | 150 | ## 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="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell> | |
| 1 | +<mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content; declared toolsbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content</font>lt;/fontbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content</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="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell> | |
| 2 | 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 | 3 | <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="<b>internal/agentsmd</b><br/><font style="font-size:10px">the project's AGENTS.md, appended to the system prompt per conversation</font>" 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="<b>internal/elicit</b><br/><font style="font-size:10px">ask_user's questions and answers: normalize, line answers, elicitation form schema</font>" 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="<b>internal/hooks</b><br/><font style="font-size:10px">the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError</font>" 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="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell> | 1 | +<mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content; declared toolsbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content</font>lt;/fontbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content</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="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell> |
| 2 | <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell> | 2 | <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell> |
| 3 | <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="<b>internal/agentsmd</b><br/><font style="font-size:10px">the project's AGENTS.md, appended to the system prompt per conversation</font>" 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="<b>internal/elicit</b><br/><font style="font-size:10px">ask_user's questions and answers: normalize, line answers, elicitation form schema</font>" 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="<b>internal/hooks</b><br/><font style="font-size:10px">the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError</font>" 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="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="<b>internal/agentsmd</b><br/><font style="font-size:10px">the project's AGENTS.md, appended to the system prompt per conversation</font>" 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="<b>internal/elicit</b><br/><font style="font-size:10px">ask_user's questions and answers: normalize, line answers, elicitation form schema</font>" 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="<b>internal/hooks</b><br/><font style="font-size:10px">the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError</font>" 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) | ||
| 25 | 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 | 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 editor |
| 27 | 27 | - [Add a skill](how-to/add-a-skill.md) |
| 28 | +- [Declare a custom tool](how-to/declare-a-custom-tool.md) | |
| 28 | 29 | - [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 | 30 | - [Keep a long session inside the context window](how-to/manage-the-context-window.md) |
| 30 | 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 | 49 | - [Command line](reference/cli.md) — flags, environment variables, start-up banner |
| 49 | 50 | - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys |
| 50 | 51 | - [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 | 53 | - [Skill file format](reference/skill-format.md) |
| 53 | 54 | - [Hooks](reference/hooks.md) — the six events, the JSON a hook reads and writes, `matcher`, `timeout`, `onError`, validation errors |
| 54 | 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 | 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 | 68 | - [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 | 69 | - [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 | 71 | - [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 | 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 | 73 | - [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 editor | 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 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-after | 29 | - [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 banner | 49 | - [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 keys | 50 | - [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 rule | 51 | - [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 errors | 54 | - [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 permission | 68 | - [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 limits | 69 | - [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 alive | 71 | - [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 it | 73 | - [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` | ||
| 248 | 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 | 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 | 272 | ## Loop detection |
| 252 | 273 | |
| 253 | 274 | 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 detection | 272 | ## 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 { | ||
| 215 | 215 | // turn (internal/hooks, ticket 36). They read one JSON object on stdin |
| 216 | 216 | // and may answer one on stdout. None by default. |
| 217 | 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 | 225 | // YoloEnv names the variable that overrides `yolo` for one run ("true", |
| @@ -804,5 +809,8 @@ func (c Config) validate() error { | ||
| 804 | 809 | if err := c.Hooks.validate(); err != nil { |
| 805 | 810 | return err |
| 806 | 811 | } |
| 812 | + if err := c.validateTools(); err != nil { | |
| 813 | + return err | |
| 814 | + } | |
| 807 | 815 | return c.LSP.validate() |
| 808 | 816 | } |
| @@ -215,6 +215,11 @@ type Config struct { | |||
| 215 | // turn (internal/hooks, ticket 36). They read one JSON object on stdin | 215 | // 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 err | 810 | 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.