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

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

Download ZIP

Add LSP support #3

Merged@k33g wants to merge feature/lsp-builtin into main
Merged as 4ff5108b36df.
52 files changed
modified .tickets/issues/0017-add-lsp-support-to-mini-me.yaml +88 -4
@@ -1,16 +1,100 @@
11 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
22 id: 17
33 title: Add LSP support to Mini Me
4-state: open
4+state: closed
55 author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-09-17T13:06:27.245Z
9-updatedAt: 2026-09-17T13:36:09.722Z
9+updatedAt: 2026-09-18T18:07:46.008Z
1010 labels:
1111 - priority::urgent
1212 - builtin tool
1313 milestone: v0.0.2
14-body: ""
15-tasks: []
14+body: |
15+ - Specify in the agent configuration file which LSP server(s) to use
16+ - Create a builtin tool for the usage of LSP server(s)
17+ - Create a documentation providing some instruction system samples that helps the agent to use correctly the LSP server
18+ - If the LSP server(s) is/are not available, display a warning message and do not use the builtin
19+
20+ ## Implementation note (Claude, 2026-09-18, branch `feature/lsp-builtin`)
21+
22+ **Config** — `lsp.servers`, a list of `{name, command, args, extensions}`; the file extension picks the server; validation refuses a missing name/command/extensions, an extension without its dot, and an extension served twice. Both shipped YAMLs carry the block commented out; the new `agent.lsp.yaml` is `agent.dmr.yaml` with gopls on, five other servers ready to uncomment (typescript-language-server, pyright, rust-analyzer, clangd, jdtls), and a system prompt section `CODE NAVIGATION (lsp)` that maps user intents to actions.
23+
24+ **Package `internal/lsp`** (on `go.lsp.dev/protocol` + `go.lsp.dev/jsonrpc2` v1.0.1, the user's choice over a hand-rolled client) — one `Client` per project; a server starts on the first call for one of its extensions (initialize/initialized, project root = the directory mm runs in), is kept for the session and stopped at exit (`shutdown`, `exit`, process). Before every request the file is re-read from disk and synced (`didOpen`, then whole-document `didChange` when it moved) so `edit_file` changes are seen. Operations: `Definition`, `References`, `Hover`, `Symbols`, `Diagnostics` (waits up to 3 s for the server's `publishDiagnostics`; stale results labelled `as last published`), `Rename` (applies the `WorkspaceEdit` to disk, UTF-16 → byte conversion, re-syncs), `Locate` (identifier → occurrences, whole word, comment lines last). Answers are text for the model: `path:line:column <source line>`. Positions 1-based at the boundary, 0-based UTF-16 on the wire.
25+
26+ **Tool `lsp`** (`internal/tools/lsp.go`) — one tool, `action` ∈ definition|references|hover|symbols|diagnostics|rename, `path`, `symbol`, `line`, `column`, `new_name`. `symbol` replaces line+column: the first end-to-end run showed the 12B model sending `column: 0` on every call; with `symbol` it answered right in two calls. Occurrences are tried in order until the server answers (misses are `lsp.ErrNoResult`). Failures come back as text. Terminal: `🔎 lsp: definition main.go greet`; ACP/window: kind `read` for navigation, kind `edit` + permission for rename. Recap counter `🔎 N lsp call(s)` (`engine.LSPCalls`) in the REPL, the window and the ACP stderr line.
27+
28+ **Availability** — `main.go`/`assembleLSP`: `exec.LookPath` per server; missing → `[warning: lsp server <name> (<command>) not found on the PATH — skipped]`; none found → `lsp: none of the configured servers is on the PATH — the lsp tool is not declared`. Servers are stopped by `defer set.close()`.
29+
30+ **Skill** — `skills/lsp-navigation/SKILL.md`: when to use it, how to point at a symbol, the actions table, a change procedure (symbols → references → edit → diagnostics → tests).
31+
32+ **Docs EN+FR** — how-to `navigate-code-with-lsp.md` (install table for 7 servers, YAML for 4, the system-prompt block, banner, a table of test prompts and what to expect, ACP/window, variants), explanation `lsp-tool.md`, reference `tools.md` (`lsp` section + counter), `configuration.md` (`lsp` keys + validation errors), architecture table + `packages.drawio` (`internal/lsp`, `go.lsp.dev` nodes and edges), README (`agent.lsp.yaml` bullet).
33+
34+ **Tests** — `internal/lsp` over an in-memory fake server (`protocol.NewServer` on `net.Pipe`): the six operations, refusals, silent server, UTF-16 conversions, `Locate`, `Available`/`Describe`; `internal/tools` dispatch/title/failure-as-text/symbol fallback; `internal/config` LSP validation; `internal/engine` counter. `go test ./...` green. End to end in the sandbox with the host's DMR model (mellum2-12b) and real `gopls` on a scratch module: definition, references, hover, symbols, diagnostics (`declared and not used`), rename (file rewritten, references answered on the new name), through `agent.lsp.yaml`.
35+
36+ **Side effect to know** — `go.lsp.dev/*` v1.0.1 require Go 1.26: `go.mod` went from `go 1.25.0` to `go 1.26` (README and tutorials updated). qlty's golangci-lint plugin runs a Go 1.22 runtime that cannot fetch a 1.26 toolchain in the sandbox, so the errcheck warnings are not measured until that runtime is updated; the smells count is complete (no new one in the LSP code after refactoring).
37+
38+ **Not done** — position encoding negotiation (UTF-16 assumed, fine for ASCII code and handled for accents; wide-char columns on the model's side are runes); workspace symbols (`workspace/symbol`), code actions, type definition, implementation; per-session workspace root over ACP (the root is the directory mm runs in); Python docstrings are not recognised as comment lines by `Locate`.
39+tasks:
40+ - id: 1
41+ title: Config `lsp.servers` (name, command, args, extensions) with validation; commented block in the shipped YAMLs; `agent.lsp.yaml`
42+ state: closed
43+ priority: none
44+ author:
45+ name: k33g
46+ email: ph.charriere@gmail.com
47+ createdAt: 2026-09-18T17:40:00.000Z
48+ updatedAt: 2026-09-18T17:40:00.000Z
49+ closedAt: 2026-09-18T17:40:00.000Z
50+ - id: 2
51+ title: "`internal/lsp` client on go.lsp.dev: lazy start, file sync from disk, definition/references/hover/symbols/diagnostics/rename, Locate by identifier; fake-server tests"
52+ state: closed
53+ priority: none
54+ author:
55+ name: k33g
56+ email: ph.charriere@gmail.com
57+ createdAt: 2026-09-18T17:40:00.000Z
58+ updatedAt: 2026-09-18T17:40:00.000Z
59+ closedAt: 2026-09-18T17:40:00.000Z
60+ - id: 3
61+ title: Builtin tool `lsp` (one tool, `action` field, `symbol` instead of columns, failures as text, rename gated by permission) + `🔎 N lsp call(s)` counter
62+ state: closed
63+ priority: none
64+ author:
65+ name: k33g
66+ email: ph.charriere@gmail.com
67+ createdAt: 2026-09-18T17:40:00.000Z
68+ updatedAt: 2026-09-18T17:40:00.000Z
69+ closedAt: 2026-09-18T17:40:00.000Z
70+ - id: 4
71+ title: "Missing server: warning at start-up and server skipped; no server found: tool not declared"
72+ state: closed
73+ priority: none
74+ author:
75+ name: k33g
76+ email: ph.charriere@gmail.com
77+ createdAt: 2026-09-18T17:40:00.000Z
78+ updatedAt: 2026-09-18T17:40:00.000Z
79+ closedAt: 2026-09-18T17:40:00.000Z
80+ - id: 5
81+ title: Docs EN+FR (how-to with system-prompt samples, install table, test prompts; explanation; tools and configuration references; architecture + drawio), skill `lsp-navigation`
82+ state: closed
83+ priority: none
84+ author:
85+ name: k33g
86+ email: ph.charriere@gmail.com
87+ createdAt: 2026-09-18T17:40:00.000Z
88+ updatedAt: 2026-09-18T17:40:00.000Z
89+ closedAt: 2026-09-18T17:40:00.000Z
90+ - id: 6
91+ title: Verify on macOS with the installed binary and gopls on a real project (definition, references, rename, diagnostics); accept the Go 1.26 floor in go.mod
92+ state: closed
93+ priority: high
94+ author:
95+ name: k33g
96+ email: ph.charriere@gmail.com
97+ createdAt: 2026-09-18T17:40:00.000Z
98+ updatedAt: 2026-09-18T18:07:40.930Z
99+ closedAt: 2026-09-18T18:07:40.930Z
16100 comments: []
@@ -1,16 +1,100 @@
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: 172 id: 17
3 title: Add LSP support to Mini Me3 title: Add LSP support to Mini Me
4-state: open4+state: closed
5 author:5 author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-09-17T13:06:27.245Z8 createdAt: 2026-09-17T13:06:27.245Z
9-updatedAt: 2026-09-17T13:36:09.722Z9+updatedAt: 2026-09-18T18:07:46.008Z
10 labels:10 labels:
11 - priority::urgent11 - priority::urgent
12 - builtin tool12 - builtin tool
13 milestone: v0.0.213 milestone: v0.0.2
14-body: ""14+body: |
15-tasks: []15+ - Specify in the agent configuration file which LSP server(s) to use
16+ - Create a builtin tool for the usage of LSP server(s)
17+ - Create a documentation providing some instruction system samples that helps the agent to use correctly the LSP server
18+ - If the LSP server(s) is/are not available, display a warning message and do not use the builtin
19+
20+ ## Implementation note (Claude, 2026-09-18, branch `feature/lsp-builtin`)
21+
22+ **Config** — `lsp.servers`, a list of `{name, command, args, extensions}`; the file extension picks the server; validation refuses a missing name/command/extensions, an extension without its dot, and an extension served twice. Both shipped YAMLs carry the block commented out; the new `agent.lsp.yaml` is `agent.dmr.yaml` with gopls on, five other servers ready to uncomment (typescript-language-server, pyright, rust-analyzer, clangd, jdtls), and a system prompt section `CODE NAVIGATION (lsp)` that maps user intents to actions.
23+
24+ **Package `internal/lsp`** (on `go.lsp.dev/protocol` + `go.lsp.dev/jsonrpc2` v1.0.1, the user's choice over a hand-rolled client) — one `Client` per project; a server starts on the first call for one of its extensions (initialize/initialized, project root = the directory mm runs in), is kept for the session and stopped at exit (`shutdown`, `exit`, process). Before every request the file is re-read from disk and synced (`didOpen`, then whole-document `didChange` when it moved) so `edit_file` changes are seen. Operations: `Definition`, `References`, `Hover`, `Symbols`, `Diagnostics` (waits up to 3 s for the server's `publishDiagnostics`; stale results labelled `as last published`), `Rename` (applies the `WorkspaceEdit` to disk, UTF-16 → byte conversion, re-syncs), `Locate` (identifier → occurrences, whole word, comment lines last). Answers are text for the model: `path:line:column <source line>`. Positions 1-based at the boundary, 0-based UTF-16 on the wire.
25+
26+ **Tool `lsp`** (`internal/tools/lsp.go`) — one tool, `action` ∈ definition|references|hover|symbols|diagnostics|rename, `path`, `symbol`, `line`, `column`, `new_name`. `symbol` replaces line+column: the first end-to-end run showed the 12B model sending `column: 0` on every call; with `symbol` it answered right in two calls. Occurrences are tried in order until the server answers (misses are `lsp.ErrNoResult`). Failures come back as text. Terminal: `🔎 lsp: definition main.go greet`; ACP/window: kind `read` for navigation, kind `edit` + permission for rename. Recap counter `🔎 N lsp call(s)` (`engine.LSPCalls`) in the REPL, the window and the ACP stderr line.
27+
28+ **Availability** — `main.go`/`assembleLSP`: `exec.LookPath` per server; missing → `[warning: lsp server <name> (<command>) not found on the PATH — skipped]`; none found → `lsp: none of the configured servers is on the PATH — the lsp tool is not declared`. Servers are stopped by `defer set.close()`.
29+
30+ **Skill** — `skills/lsp-navigation/SKILL.md`: when to use it, how to point at a symbol, the actions table, a change procedure (symbols → references → edit → diagnostics → tests).
31+
32+ **Docs EN+FR** — how-to `navigate-code-with-lsp.md` (install table for 7 servers, YAML for 4, the system-prompt block, banner, a table of test prompts and what to expect, ACP/window, variants), explanation `lsp-tool.md`, reference `tools.md` (`lsp` section + counter), `configuration.md` (`lsp` keys + validation errors), architecture table + `packages.drawio` (`internal/lsp`, `go.lsp.dev` nodes and edges), README (`agent.lsp.yaml` bullet).
33+
34+ **Tests** — `internal/lsp` over an in-memory fake server (`protocol.NewServer` on `net.Pipe`): the six operations, refusals, silent server, UTF-16 conversions, `Locate`, `Available`/`Describe`; `internal/tools` dispatch/title/failure-as-text/symbol fallback; `internal/config` LSP validation; `internal/engine` counter. `go test ./...` green. End to end in the sandbox with the host's DMR model (mellum2-12b) and real `gopls` on a scratch module: definition, references, hover, symbols, diagnostics (`declared and not used`), rename (file rewritten, references answered on the new name), through `agent.lsp.yaml`.
35+
36+ **Side effect to know** — `go.lsp.dev/*` v1.0.1 require Go 1.26: `go.mod` went from `go 1.25.0` to `go 1.26` (README and tutorials updated). qlty's golangci-lint plugin runs a Go 1.22 runtime that cannot fetch a 1.26 toolchain in the sandbox, so the errcheck warnings are not measured until that runtime is updated; the smells count is complete (no new one in the LSP code after refactoring).
37+
38+ **Not done** — position encoding negotiation (UTF-16 assumed, fine for ASCII code and handled for accents; wide-char columns on the model's side are runes); workspace symbols (`workspace/symbol`), code actions, type definition, implementation; per-session workspace root over ACP (the root is the directory mm runs in); Python docstrings are not recognised as comment lines by `Locate`.
39+tasks:
40+ - id: 1
41+ title: Config `lsp.servers` (name, command, args, extensions) with validation; commented block in the shipped YAMLs; `agent.lsp.yaml`
42+ state: closed
43+ priority: none
44+ author:
45+ name: k33g
46+ email: ph.charriere@gmail.com
47+ createdAt: 2026-09-18T17:40:00.000Z
48+ updatedAt: 2026-09-18T17:40:00.000Z
49+ closedAt: 2026-09-18T17:40:00.000Z
50+ - id: 2
51+ title: "`internal/lsp` client on go.lsp.dev: lazy start, file sync from disk, definition/references/hover/symbols/diagnostics/rename, Locate by identifier; fake-server tests"
52+ state: closed
53+ priority: none
54+ author:
55+ name: k33g
56+ email: ph.charriere@gmail.com
57+ createdAt: 2026-09-18T17:40:00.000Z
58+ updatedAt: 2026-09-18T17:40:00.000Z
59+ closedAt: 2026-09-18T17:40:00.000Z
60+ - id: 3
61+ title: Builtin tool `lsp` (one tool, `action` field, `symbol` instead of columns, failures as text, rename gated by permission) + `🔎 N lsp call(s)` counter
62+ state: closed
63+ priority: none
64+ author:
65+ name: k33g
66+ email: ph.charriere@gmail.com
67+ createdAt: 2026-09-18T17:40:00.000Z
68+ updatedAt: 2026-09-18T17:40:00.000Z
69+ closedAt: 2026-09-18T17:40:00.000Z
70+ - id: 4
71+ title: "Missing server: warning at start-up and server skipped; no server found: tool not declared"
72+ state: closed
73+ priority: none
74+ author:
75+ name: k33g
76+ email: ph.charriere@gmail.com
77+ createdAt: 2026-09-18T17:40:00.000Z
78+ updatedAt: 2026-09-18T17:40:00.000Z
79+ closedAt: 2026-09-18T17:40:00.000Z
80+ - id: 5
81+ title: Docs EN+FR (how-to with system-prompt samples, install table, test prompts; explanation; tools and configuration references; architecture + drawio), skill `lsp-navigation`
82+ state: closed
83+ priority: none
84+ author:
85+ name: k33g
86+ email: ph.charriere@gmail.com
87+ createdAt: 2026-09-18T17:40:00.000Z
88+ updatedAt: 2026-09-18T17:40:00.000Z
89+ closedAt: 2026-09-18T17:40:00.000Z
90+ - id: 6
91+ title: Verify on macOS with the installed binary and gopls on a real project (definition, references, rename, diagnostics); accept the Go 1.26 floor in go.mod
92+ state: closed
93+ priority: high
94+ author:
95+ name: k33g
96+ email: ph.charriere@gmail.com
97+ createdAt: 2026-09-18T17:40:00.000Z
98+ updatedAt: 2026-09-18T18:07:40.930Z
99+ closedAt: 2026-09-18T18:07:40.930Z
16 comments: []100 comments: []
modified .vscode/settings.json +1 -1
@@ -59,5 +59,5 @@
5959 "ui.semanticTokens": true,
6060 "ui.completion.usePlaceholders": true
6161 },
62- "issuespec.groupBy": "label"
62+ "issuespec.groupBy": "milestone"
6363 }
\ No newline at end of file
@@ -59,5 +59,5 @@
59 "ui.semanticTokens": true,59 "ui.semanticTokens": true,
60 "ui.completion.usePlaceholders": true60 "ui.completion.usePlaceholders": true
61 },61 },
62- "issuespec.groupBy": "label"62+ "issuespec.groupBy": "milestone"
63 }63 }
\ No newline at end of file\ No newline at end of file
modified README.md +3 -2
@@ -12,7 +12,7 @@ From there it grew into the coding agent I wanted for local models (Docker Model
1212
1313 - **hallucinations** — the agent shows the real output of every command before the model comments on it, and the prompt forbids stating anything a command did not return;
1414 - **silent failures** — a failing command, a refused edit or a rejected permission goes back to the model as text, so it reads why and tries something else;
15-- **endless loops** — the same action with the same result, repeated, is detected and the model is told to change approach; a watchdog catches a connection that went silent.
15+- **endless loops** — the same action with the same result, repeated, is detected and the model is told to change approach; if it keeps repeating anyway, the turn is stopped and the error is recorded in the session; a watchdog catches a connection that went silent.
1616
1717 What makes Mini Me a bit special is that it was designed from the start to be used **from an editor through the [Agent Client Protocol (ACP)](https://agentclientprotocol.com)**: the same loop runs in a terminal REPL, in a full-screen terminal window, and inside Zed.
1818
@@ -20,7 +20,7 @@ What makes Mini Me a bit special is that it was designed from the start to be us
2020
2121 ## Install
2222
23-You need Go 1.25+ and a model server that can emit tool calls: Docker Model Runner (default) or Llamacpp.
23+You need Go 1.26+ and a model server that can emit tool calls: Docker Model Runner (default) or Llamacpp.
2424
2525 ```bash
2626 git clone https://git.rickub.com/bots-garden/mini-me.git
@@ -97,3 +97,4 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
9797
9898 - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.
9999 - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.
100+- `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md).
@@ -12,7 +12,7 @@ From there it grew into the coding agent I wanted for local models (Docker Model
12 12
13 - **hallucinations** — the agent shows the real output of every command before the model comments on it, and the prompt forbids stating anything a command did not return;13 - **hallucinations** — the agent shows the real output of every command before the model comments on it, and the prompt forbids stating anything a command did not return;
14 - **silent failures** — a failing command, a refused edit or a rejected permission goes back to the model as text, so it reads why and tries something else;14 - **silent failures** — a failing command, a refused edit or a rejected permission goes back to the model as text, so it reads why and tries something else;
15-- **endless loops** — the same action with the same result, repeated, is detected and the model is told to change approach; a watchdog catches a connection that went silent.15+- **endless loops** — the same action with the same result, repeated, is detected and the model is told to change approach; if it keeps repeating anyway, the turn is stopped and the error is recorded in the session; a watchdog catches a connection that went silent.
16 16
17 What makes Mini Me a bit special is that it was designed from the start to be used **from an editor through the [Agent Client Protocol (ACP)](https://agentclientprotocol.com)**: the same loop runs in a terminal REPL, in a full-screen terminal window, and inside Zed.17 What makes Mini Me a bit special is that it was designed from the start to be used **from an editor through the [Agent Client Protocol (ACP)](https://agentclientprotocol.com)**: the same loop runs in a terminal REPL, in a full-screen terminal window, and inside Zed.
18 18
@@ -20,7 +20,7 @@ What makes Mini Me a bit special is that it was designed from the start to be us
20 20
21 ## Install21 ## Install
22 22
23-You need Go 1.25+ and a model server that can emit tool calls: Docker Model Runner (default) or Llamacpp.23+You need Go 1.26+ and a model server that can emit tool calls: Docker Model Runner (default) or Llamacpp.
24 24
25 ```bash25 ```bash
26 git clone https://git.rickub.com/bots-garden/mini-me.git26 git clone https://git.rickub.com/bots-garden/mini-me.git
@@ -97,3 +97,4 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
97 97
98 - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.98 - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.
99 - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.99 - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.
100+- `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md).
modified agent.dmr.yaml +20 -0
@@ -27,6 +27,26 @@ bashTool: true
2727 # The built-in file tools: read_file, write_file, edit_file.
2828 editTools: true
2929
30+# Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
31+# The file extension picks the server. A server missing from the PATH is skipped with a warning.
32+# No server listed = no `lsp` tool. Examples (uncomment what you have installed):
33+# lsp:
34+# servers:
35+# - name: gopls
36+# command: gopls
37+# extensions: [".go"]
38+# - name: typescript
39+# command: typescript-language-server
40+# args: ["--stdio"]
41+# extensions: [".ts", ".tsx", ".js", ".jsx"]
42+# - name: python
43+# command: pyright-langserver
44+# args: ["--stdio"]
45+# extensions: [".py"]
46+# - name: rust
47+# command: rust-analyzer
48+# extensions: [".rs"]
49+
3050 # Where the `read_skill` tool looks for its markdown procedures.
3151 # The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.
3252 skillsDir: skills
@@ -27,6 +27,26 @@ bashTool: true
27 # The built-in file tools: read_file, write_file, edit_file. 27 # The built-in file tools: read_file, write_file, edit_file.
28 editTools: true28 editTools: true
29 29
30+# Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
31+# The file extension picks the server. A server missing from the PATH is skipped with a warning.
32+# No server listed = no `lsp` tool. Examples (uncomment what you have installed):
33+# lsp:
34+# servers:
35+# - name: gopls
36+# command: gopls
37+# extensions: [".go"]
38+# - name: typescript
39+# command: typescript-language-server
40+# args: ["--stdio"]
41+# extensions: [".ts", ".tsx", ".js", ".jsx"]
42+# - name: python
43+# command: pyright-langserver
44+# args: ["--stdio"]
45+# extensions: [".py"]
46+# - name: rust
47+# command: rust-analyzer
48+# extensions: [".rs"]
49+
30 # Where the `read_skill` tool looks for its markdown procedures. 50 # Where the `read_skill` tool looks for its markdown procedures.
31 # The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.51 # The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.
32 skillsDir: skills52 skillsDir: skills
modified agent.llamacpp.yaml +20 -0
@@ -23,6 +23,26 @@ bashTool: true
2323 # The built-in file tools: read_file, write_file, edit_file.
2424 editTools: true
2525
26+# Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
27+# The file extension picks the server. A server missing from the PATH is skipped with a warning.
28+# No server listed = no `lsp` tool. Examples (uncomment what you have installed):
29+# lsp:
30+# servers:
31+# - name: gopls
32+# command: gopls
33+# extensions: [".go"]
34+# - name: typescript
35+# command: typescript-language-server
36+# args: ["--stdio"]
37+# extensions: [".ts", ".tsx", ".js", ".jsx"]
38+# - name: python
39+# command: pyright-langserver
40+# args: ["--stdio"]
41+# extensions: [".py"]
42+# - name: rust
43+# command: rust-analyzer
44+# extensions: [".rs"]
45+
2646 # How many tokens the server actually serves.
2747 # 0 = ask the server (llama-server tells it on /props)
2848 contextWindow: 0
@@ -23,6 +23,26 @@ bashTool: true
23 # The built-in file tools: read_file, write_file, edit_file. 23 # The built-in file tools: read_file, write_file, edit_file.
24 editTools: true24 editTools: true
25 25
26+# Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
27+# The file extension picks the server. A server missing from the PATH is skipped with a warning.
28+# No server listed = no `lsp` tool. Examples (uncomment what you have installed):
29+# lsp:
30+# servers:
31+# - name: gopls
32+# command: gopls
33+# extensions: [".go"]
34+# - name: typescript
35+# command: typescript-language-server
36+# args: ["--stdio"]
37+# extensions: [".ts", ".tsx", ".js", ".jsx"]
38+# - name: python
39+# command: pyright-langserver
40+# args: ["--stdio"]
41+# extensions: [".py"]
42+# - name: rust
43+# command: rust-analyzer
44+# extensions: [".rs"]
45+
26 # How many tokens the server actually serves.46 # How many tokens the server actually serves.
27 # 0 = ask the server (llama-server tells it on /props)47 # 0 = ask the server (llama-server tells it on /props)
28 contextWindow: 048 contextWindow: 0
added agent.lsp.yaml +203 -0
new file mode 100644
@@ -0,0 +1,203 @@
1+# Agent settings — the LSP variant of agent.dmr.yaml.
2+#
3+# Same model and server as agent.dmr.yaml; the difference is the `lsp` block
4+# below (gopls on, the other servers ready to uncomment) and a system prompt
5+# that tells the model WHEN to ask the language server instead of grepping.
6+#
7+# mm agent.lsp.yaml # line mode
8+# mm -tui agent.lsp.yaml # window mode
9+#
10+provider: dmr
11+
12+model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
13+baseUrl: http://localhost:12434/engines/v1
14+fallback: http://host.docker.internal:12434/engines/v1
15+
16+
17+# How many tokens the server actually serves.
18+# 0 = ask the server
19+contextWindow: 0
20+
21+# Max number of characters a tool returns to the model.
22+# Context safeguard: beyond that, the output is truncated (beginning + end kept).
23+maxOutput: 16000
24+
25+# Max number of model <-> tools round trips for a single question.
26+# Raise it for tasks that need long chains of commands.
27+maxTurns: 40
28+
29+# The `bash` tool.
30+# `false` removes it: the model can still read and edit files (editTools) and load skills,
31+# but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".
32+bashTool: true
33+
34+# The built-in file tools: read_file, write_file, edit_file.
35+editTools: true
36+
37+# Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
38+# The file extension picks the server. A server missing from the PATH is skipped with a warning at start-up;
39+# with no server on the PATH the tool is not declared. Install what you use, uncomment its entry.
40+lsp:
41+ servers:
42+ - name: gopls # go install golang.org/x/tools/gopls@latest
43+ command: gopls
44+ extensions: [".go"]
45+ # - name: typescript # npm i -g typescript typescript-language-server
46+ # command: typescript-language-server
47+ # args: ["--stdio"]
48+ # extensions: [".ts", ".tsx", ".js", ".jsx"]
49+ # - name: python # pip install pyright (or: npm i -g pyright)
50+ # command: pyright-langserver
51+ # args: ["--stdio"]
52+ # extensions: [".py"]
53+ # - name: rust # rustup component add rust-analyzer
54+ # command: rust-analyzer
55+ # extensions: [".rs"]
56+ # - name: clangd # apt install clangd / brew install llvm
57+ # command: clangd
58+ # extensions: [".c", ".h", ".cc", ".cpp", ".hpp"]
59+ # - name: java # jdtls (Eclipse JDT language server), or another Java server
60+ # command: jdtls
61+ # extensions: [".java"]
62+
63+# Where the `read_skill` tool looks for its markdown procedures.
64+# The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.
65+skillsDir: skills
66+
67+# Persistent sessions, per project.
68+# When enabled, every conversation is saved as one JSON file under `dir`
69+sessions:
70+ enabled: true
71+ dir: .mm/sessions
72+
73+# How many lines of a command's output are echoed to the terminal.
74+# 0 disables it.
75+previewLines: 20
76+
77+# The system prompt: what the agent is, and what it is allowed to do.
78+# This is the knob to play with — it is the shortest path to changing behaviour.
79+system: |
80+ Your name is Bob.
81+ You are a coding agent working in a terminal.
82+ You have a "bash" tool to run shell commands, file tools to read and edit,
83+ and an "lsp" tool to ask the language server about the code.
84+ Use bash to run tests, build, list and search; use lsp for anything about a symbol.
85+ Chain several commands if needed, then answer clearly in English.
86+
87+ A request often mixes things you answer from yourself ("say hello") with
88+ things only a command can answer ("list the files"). Handle every part, in
89+ the order asked, and run a command for each part that needs one.
90+ Never state the contents of a file, the output of a command, or the state of
91+ the repository unless a command in THIS answer returned it. What you did not
92+ read, you do not know: run the command instead of recalling it.
93+
94+ SKILLS
95+ You have a second tool, `read_skill`. Its description lists the procedures
96+ available for this project — one per kind of task.
97+
98+ Any request to DO something to a Go project is a skill, not a shell command
99+ you invent. Match the request against that list, call `read_skill` FIRST,
100+ before any bash command, and then follow what it says step by step.
101+
102+ FILE EDITING
103+ You have three tools for files: `read_file`, `edit_file` and `write_file`.
104+ They are how a file gets read and changed here: each change is exact,
105+ checked before it is written, and comes back as a diff with line numbers.
106+ bash is for running things — building, testing, listing, searching.
107+
108+ - Read before you write: call `read_file` on the file (numbered=true when
109+ you need line numbers). You cannot target text you have not seen; never
110+ rely on what you think you remember about a file.
111+ - To change an existing file, call `edit_file` with one or more {old, new}
112+ pairs. `old` is copied from the file character for character — same
113+ spaces, same indentation, same line breaks — and appears exactly once:
114+ add the surrounding lines until it is unique. Several pairs are applied
115+ together, against the original file. An empty `new` deletes the text.
116+ - Call `write_file` only to create a file, or to rewrite one entirely and
117+ on purpose. On an existing file it replaces everything, including what
118+ you did not intend to touch.
119+ - Read the diff the tool returns: it says exactly what changed and on which
120+ line. If `edit_file` refuses — text not found, ambiguous, overlapping
121+ edits — read the file again and fix `old`. Do not fall back to
122+ `write_file` to force the change through.
123+ - After editing code, run the narrowest check with bash: the formatter, the
124+ compiler, or the test covering that file.
125+
126+ CODE NAVIGATION (lsp)
127+ You have an `lsp` tool: it asks the project's language server what the
128+ compiler knows. Its description says which file extensions it covers.
129+ Use it INSTEAD of grep or reading whole files for anything about a symbol:
130+ - "where is X defined / declared" → action definition, symbol X
131+ - "who calls / uses X", "find usages" → action references, symbol X
132+ - "what is the type / signature of X" → action hover, symbol X
133+ - "what does this file contain" → action symbols, path only
134+ - "are there errors", after every edit → action diagnostics, path only
135+ - "rename X to Y everywhere" → action rename, symbol X, new_name Y
136+ (rename NEEDS new_name — the new identifier; never rename with edit_file:
137+ {"action": "rename", "path": "main.go", "symbol": "Greet", "new_name": "Hello"})
138+ Point at a symbol with its NAME (the `symbol` field), exactly as written in
139+ the code; add `line` only to pick one occurrence among several. Do not
140+ guess columns. Answers are "path:line:column <source line>": quote them,
141+ they are the evidence.
142+ Before changing a symbol used in several files, call references and say how
143+ many places are affected. After editing, call diagnostics on each edited
144+ file and fix what it reports before running tests. A `[lsp: …]` answer is
145+ information: "no language server for" means use bash; "not found in" means
146+ the name is not spelled as in the code — read the file and copy it.
147+
148+ RULES
149+ - Keep everything the file already does, unless the user asked to remove it.
150+ - Touch only the files the request is about. Do not add tests, files or
151+ features that were not asked for.
152+ - Never run a git command unless the user says git, commit or push.
153+ - Never move, rename or delete a file unless the user asked for it.
154+ - Then answer in English, in a few lines.
155+ - If you don't know how to use a <cli>, run `<cli> --help` (or `<cli> help`)
156+ to understand the options, then run the command.
157+
158+ BACKGROUND JOBS
159+ Never let a command block the answer. Anything that serves, watches or runs
160+ long goes to the background, with BOTH streams redirected and its pid kept:
161+
162+ nohup <command> > /tmp/<job>.log 2>&1 & echo $! > /tmp/<job>.pid
163+
164+ Redirecting only stdout still blocks until the process exits. Read the
165+ `bg-jobs` skill before you wait on, inspect or stop such a job — each has a
166+ rule you cannot guess. Stop every job you started before you finish, and say
167+ which ones you left running.
168+
169+# Generation settings (OpenAI API keys). Low temperature for a coding agent:
170+# we want precise and reproducible answers, not creativity.
171+sampling:
172+ temperature: 0.0
173+ parallel_tool_calls: false
174+ top_p: 0.9
175+ max_tokens: 4096
176+
177+watchdogTimeout: 30s
178+
179+# Context compression
180+context:
181+ enabled: false
182+
183+ # Compress when the history reaches this share of the window.
184+ threshold: 75
185+
186+ # Fallback on the message count, for when the window is unknown.
187+ # One command costs 2 messages (call + response), so 80 is roughly 30 commands of history.
188+ maxMessages: 80
189+
190+ # How many recent questions (with their commands and answers) are kept as-is instead of being summarised.
191+ # The model works on the last few turns, so summarising them would make it redo what it just did.
192+ keepLastTurns: 3
193+
194+ # max_tokens of the summary request.
195+ summaryMaxTokens: 1200
196+
197+ # Replaces the built-in summary prompt when set.
198+ # Empty = built-in.
199+ prompt: ""
200+
201+ showStats: true
202+
203+displayCommands: true
new file mode 100644
@@ -0,0 +1,203 @@
1+# Agent settings — the LSP variant of agent.dmr.yaml.
2+#
3+# Same model and server as agent.dmr.yaml; the difference is the `lsp` block
4+# below (gopls on, the other servers ready to uncomment) and a system prompt
5+# that tells the model WHEN to ask the language server instead of grepping.
6+#
7+# mm agent.lsp.yaml # line mode
8+# mm -tui agent.lsp.yaml # window mode
9+#
10+provider: dmr
11+
12+model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
13+baseUrl: http://localhost:12434/engines/v1
14+fallback: http://host.docker.internal:12434/engines/v1
15+
16+
17+# How many tokens the server actually serves.
18+# 0 = ask the server
19+contextWindow: 0
20+
21+# Max number of characters a tool returns to the model.
22+# Context safeguard: beyond that, the output is truncated (beginning + end kept).
23+maxOutput: 16000
24+
25+# Max number of model <-> tools round trips for a single question.
26+# Raise it for tasks that need long chains of commands.
27+maxTurns: 40
28+
29+# The `bash` tool.
30+# `false` removes it: the model can still read and edit files (editTools) and load skills,
31+# but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".
32+bashTool: true
33+
34+# The built-in file tools: read_file, write_file, edit_file.
35+editTools: true
36+
37+# Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
38+# The file extension picks the server. A server missing from the PATH is skipped with a warning at start-up;
39+# with no server on the PATH the tool is not declared. Install what you use, uncomment its entry.
40+lsp:
41+ servers:
42+ - name: gopls # go install golang.org/x/tools/gopls@latest
43+ command: gopls
44+ extensions: [".go"]
45+ # - name: typescript # npm i -g typescript typescript-language-server
46+ # command: typescript-language-server
47+ # args: ["--stdio"]
48+ # extensions: [".ts", ".tsx", ".js", ".jsx"]
49+ # - name: python # pip install pyright (or: npm i -g pyright)
50+ # command: pyright-langserver
51+ # args: ["--stdio"]
52+ # extensions: [".py"]
53+ # - name: rust # rustup component add rust-analyzer
54+ # command: rust-analyzer
55+ # extensions: [".rs"]
56+ # - name: clangd # apt install clangd / brew install llvm
57+ # command: clangd
58+ # extensions: [".c", ".h", ".cc", ".cpp", ".hpp"]
59+ # - name: java # jdtls (Eclipse JDT language server), or another Java server
60+ # command: jdtls
61+ # extensions: [".java"]
62+
63+# Where the `read_skill` tool looks for its markdown procedures.
64+# The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.
65+skillsDir: skills
66+
67+# Persistent sessions, per project.
68+# When enabled, every conversation is saved as one JSON file under `dir`
69+sessions:
70+ enabled: true
71+ dir: .mm/sessions
72+
73+# How many lines of a command's output are echoed to the terminal.
74+# 0 disables it.
75+previewLines: 20
76+
77+# The system prompt: what the agent is, and what it is allowed to do.
78+# This is the knob to play with — it is the shortest path to changing behaviour.
79+system: |
80+ Your name is Bob.
81+ You are a coding agent working in a terminal.
82+ You have a "bash" tool to run shell commands, file tools to read and edit,
83+ and an "lsp" tool to ask the language server about the code.
84+ Use bash to run tests, build, list and search; use lsp for anything about a symbol.
85+ Chain several commands if needed, then answer clearly in English.
86+
87+ A request often mixes things you answer from yourself ("say hello") with
88+ things only a command can answer ("list the files"). Handle every part, in
89+ the order asked, and run a command for each part that needs one.
90+ Never state the contents of a file, the output of a command, or the state of
91+ the repository unless a command in THIS answer returned it. What you did not
92+ read, you do not know: run the command instead of recalling it.
93+
94+ SKILLS
95+ You have a second tool, `read_skill`. Its description lists the procedures
96+ available for this project — one per kind of task.
97+
98+ Any request to DO something to a Go project is a skill, not a shell command
99+ you invent. Match the request against that list, call `read_skill` FIRST,
100+ before any bash command, and then follow what it says step by step.
101+
102+ FILE EDITING
103+ You have three tools for files: `read_file`, `edit_file` and `write_file`.
104+ They are how a file gets read and changed here: each change is exact,
105+ checked before it is written, and comes back as a diff with line numbers.
106+ bash is for running things — building, testing, listing, searching.
107+
108+ - Read before you write: call `read_file` on the file (numbered=true when
109+ you need line numbers). You cannot target text you have not seen; never
110+ rely on what you think you remember about a file.
111+ - To change an existing file, call `edit_file` with one or more {old, new}
112+ pairs. `old` is copied from the file character for character — same
113+ spaces, same indentation, same line breaks — and appears exactly once:
114+ add the surrounding lines until it is unique. Several pairs are applied
115+ together, against the original file. An empty `new` deletes the text.
116+ - Call `write_file` only to create a file, or to rewrite one entirely and
117+ on purpose. On an existing file it replaces everything, including what
118+ you did not intend to touch.
119+ - Read the diff the tool returns: it says exactly what changed and on which
120+ line. If `edit_file` refuses — text not found, ambiguous, overlapping
121+ edits — read the file again and fix `old`. Do not fall back to
122+ `write_file` to force the change through.
123+ - After editing code, run the narrowest check with bash: the formatter, the
124+ compiler, or the test covering that file.
125+
126+ CODE NAVIGATION (lsp)
127+ You have an `lsp` tool: it asks the project's language server what the
128+ compiler knows. Its description says which file extensions it covers.
129+ Use it INSTEAD of grep or reading whole files for anything about a symbol:
130+ - "where is X defined / declared" → action definition, symbol X
131+ - "who calls / uses X", "find usages" → action references, symbol X
132+ - "what is the type / signature of X" → action hover, symbol X
133+ - "what does this file contain" → action symbols, path only
134+ - "are there errors", after every edit → action diagnostics, path only
135+ - "rename X to Y everywhere" → action rename, symbol X, new_name Y
136+ (rename NEEDS new_name — the new identifier; never rename with edit_file:
137+ {"action": "rename", "path": "main.go", "symbol": "Greet", "new_name": "Hello"})
138+ Point at a symbol with its NAME (the `symbol` field), exactly as written in
139+ the code; add `line` only to pick one occurrence among several. Do not
140+ guess columns. Answers are "path:line:column <source line>": quote them,
141+ they are the evidence.
142+ Before changing a symbol used in several files, call references and say how
143+ many places are affected. After editing, call diagnostics on each edited
144+ file and fix what it reports before running tests. A `[lsp: …]` answer is
145+ information: "no language server for" means use bash; "not found in" means
146+ the name is not spelled as in the code — read the file and copy it.
147+
148+ RULES
149+ - Keep everything the file already does, unless the user asked to remove it.
150+ - Touch only the files the request is about. Do not add tests, files or
151+ features that were not asked for.
152+ - Never run a git command unless the user says git, commit or push.
153+ - Never move, rename or delete a file unless the user asked for it.
154+ - Then answer in English, in a few lines.
155+ - If you don't know how to use a <cli>, run `<cli> --help` (or `<cli> help`)
156+ to understand the options, then run the command.
157+
158+ BACKGROUND JOBS
159+ Never let a command block the answer. Anything that serves, watches or runs
160+ long goes to the background, with BOTH streams redirected and its pid kept:
161+
162+ nohup <command> > /tmp/<job>.log 2>&1 & echo $! > /tmp/<job>.pid
163+
164+ Redirecting only stdout still blocks until the process exits. Read the
165+ `bg-jobs` skill before you wait on, inspect or stop such a job — each has a
166+ rule you cannot guess. Stop every job you started before you finish, and say
167+ which ones you left running.
168+
169+# Generation settings (OpenAI API keys). Low temperature for a coding agent:
170+# we want precise and reproducible answers, not creativity.
171+sampling:
172+ temperature: 0.0
173+ parallel_tool_calls: false
174+ top_p: 0.9
175+ max_tokens: 4096
176+
177+watchdogTimeout: 30s
178+
179+# Context compression
180+context:
181+ enabled: false
182+
183+ # Compress when the history reaches this share of the window.
184+ threshold: 75
185+
186+ # Fallback on the message count, for when the window is unknown.
187+ # One command costs 2 messages (call + response), so 80 is roughly 30 commands of history.
188+ maxMessages: 80
189+
190+ # How many recent questions (with their commands and answers) are kept as-is instead of being summarised.
191+ # The model works on the last few turns, so summarising them would make it redo what it just did.
192+ keepLastTurns: 3
193+
194+ # max_tokens of the summary request.
195+ summaryMaxTokens: 1200
196+
197+ # Replaces the built-in summary prompt when set.
198+ # Empty = built-in.
199+ prompt: ""
200+
201+ showStats: true
202+
203+displayCommands: true
added demo/agent.lsp.yaml +201 -0
new file mode 100644
@@ -0,0 +1,201 @@
1+# Agent settings — the LSP variant of agent.dmr.yaml.
2+#
3+# Same model and server as agent.dmr.yaml; the difference is the `lsp` block
4+# below (gopls on, the other servers ready to uncomment) and a system prompt
5+# that tells the model WHEN to ask the language server instead of grepping.
6+#
7+# mm agent.lsp.yaml # line mode
8+# mm -tui agent.lsp.yaml # window mode
9+#
10+provider: dmr
11+
12+model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
13+baseUrl: http://localhost:12434/engines/v1
14+fallback: http://host.docker.internal:12434/engines/v1
15+
16+
17+# How many tokens the server actually serves.
18+# 0 = ask the server
19+contextWindow: 0
20+
21+# Max number of characters a tool returns to the model.
22+# Context safeguard: beyond that, the output is truncated (beginning + end kept).
23+maxOutput: 16000
24+
25+# Max number of model <-> tools round trips for a single question.
26+# Raise it for tasks that need long chains of commands.
27+maxTurns: 40
28+
29+# The `bash` tool.
30+# `false` removes it: the model can still read and edit files (editTools) and load skills,
31+# but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".
32+bashTool: true
33+
34+# The built-in file tools: read_file, write_file, edit_file.
35+editTools: true
36+
37+# Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
38+# The file extension picks the server. A server missing from the PATH is skipped with a warning at start-up;
39+# with no server on the PATH the tool is not declared. Install what you use, uncomment its entry.
40+lsp:
41+ servers:
42+ - name: gopls # go install golang.org/x/tools/gopls@latest
43+ command: gopls
44+ extensions: [".go"]
45+ # - name: typescript # npm i -g typescript typescript-language-server
46+ # command: typescript-language-server
47+ # args: ["--stdio"]
48+ # extensions: [".ts", ".tsx", ".js", ".jsx"]
49+ # - name: python # pip install pyright (or: npm i -g pyright)
50+ # command: pyright-langserver
51+ # args: ["--stdio"]
52+ # extensions: [".py"]
53+ # - name: rust # rustup component add rust-analyzer
54+ # command: rust-analyzer
55+ # extensions: [".rs"]
56+ # - name: clangd # apt install clangd / brew install llvm
57+ # command: clangd
58+ # extensions: [".c", ".h", ".cc", ".cpp", ".hpp"]
59+ # - name: java # jdtls (Eclipse JDT language server), or another Java server
60+ # command: jdtls
61+ # extensions: [".java"]
62+
63+# Where the `read_skill` tool looks for its markdown procedures.
64+# The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.
65+skillsDir: skills
66+
67+# Persistent sessions, per project.
68+# When enabled, every conversation is saved as one JSON file under `dir`
69+sessions:
70+ enabled: true
71+ dir: .mm/sessions
72+
73+# How many lines of a command's output are echoed to the terminal.
74+# 0 disables it.
75+previewLines: 20
76+
77+# The system prompt: what the agent is, and what it is allowed to do.
78+# This is the knob to play with — it is the shortest path to changing behaviour.
79+system: |
80+ Your name is Bob.
81+ You are a coding agent working in a terminal.
82+ You have a "bash" tool to run shell commands, file tools to read and edit,
83+ and an "lsp" tool to ask the language server about the code.
84+ Use bash to run tests, build, list and search; use lsp for anything about a symbol.
85+ Chain several commands if needed, then answer clearly in English.
86+
87+ A request often mixes things you answer from yourself ("say hello") with
88+ things only a command can answer ("list the files"). Handle every part, in
89+ the order asked, and run a command for each part that needs one.
90+ Never state the contents of a file, the output of a command, or the state of
91+ the repository unless a command in THIS answer returned it. What you did not
92+ read, you do not know: run the command instead of recalling it.
93+
94+ SKILLS
95+ You have a second tool, `read_skill`. Its description lists the procedures
96+ available for this project — one per kind of task.
97+
98+ Any request to DO something to a Go project is a skill, not a shell command
99+ you invent. Match the request against that list, call `read_skill` FIRST,
100+ before any bash command, and then follow what it says step by step.
101+
102+ FILE EDITING
103+ You have three tools for files: `read_file`, `edit_file` and `write_file`.
104+ They are how a file gets read and changed here: each change is exact,
105+ checked before it is written, and comes back as a diff with line numbers.
106+ bash is for running things — building, testing, listing, searching.
107+
108+ - Read before you write: call `read_file` on the file (numbered=true when
109+ you need line numbers). You cannot target text you have not seen; never
110+ rely on what you think you remember about a file.
111+ - To change an existing file, call `edit_file` with one or more {old, new}
112+ pairs. `old` is copied from the file character for character — same
113+ spaces, same indentation, same line breaks — and appears exactly once:
114+ add the surrounding lines until it is unique. Several pairs are applied
115+ together, against the original file. An empty `new` deletes the text.
116+ - Call `write_file` only to create a file, or to rewrite one entirely and
117+ on purpose. On an existing file it replaces everything, including what
118+ you did not intend to touch.
119+ - Read the diff the tool returns: it says exactly what changed and on which
120+ line. If `edit_file` refuses — text not found, ambiguous, overlapping
121+ edits — read the file again and fix `old`. Do not fall back to
122+ `write_file` to force the change through.
123+ - After editing code, run the narrowest check with bash: the formatter, the
124+ compiler, or the test covering that file.
125+
126+ CODE NAVIGATION (lsp)
127+ You have an `lsp` tool: it asks the project's language server what the
128+ compiler knows. Its description says which file extensions it covers.
129+ Use it INSTEAD of grep or reading whole files for anything about a symbol:
130+ - "where is X defined / declared" → action definition, symbol X
131+ - "who calls / uses X", "find usages" → action references, symbol X
132+ - "what is the type / signature of X" → action hover, symbol X
133+ - "what does this file contain" → action symbols, path only
134+ - "are there errors", after every edit → action diagnostics, path only
135+ - "rename X to Y everywhere" → action rename, symbol X, new_name Y
136+ Point at a symbol with its NAME (the `symbol` field), exactly as written in
137+ the code; add `line` only to pick one occurrence among several. Do not
138+ guess columns. Answers are "path:line:column <source line>": quote them,
139+ they are the evidence.
140+ Before changing a symbol used in several files, call references and say how
141+ many places are affected. After editing, call diagnostics on each edited
142+ file and fix what it reports before running tests. A `[lsp: …]` answer is
143+ information: "no language server for" means use bash; "not found in" means
144+ the name is not spelled as in the code — read the file and copy it.
145+
146+ RULES
147+ - Keep everything the file already does, unless the user asked to remove it.
148+ - Touch only the files the request is about. Do not add tests, files or
149+ features that were not asked for.
150+ - Never run a git command unless the user says git, commit or push.
151+ - Never move, rename or delete a file unless the user asked for it.
152+ - Then answer in English, in a few lines.
153+ - If you don't know how to use a <cli>, run `<cli> --help` (or `<cli> help`)
154+ to understand the options, then run the command.
155+
156+ BACKGROUND JOBS
157+ Never let a command block the answer. Anything that serves, watches or runs
158+ long goes to the background, with BOTH streams redirected and its pid kept:
159+
160+ nohup <command> > /tmp/<job>.log 2>&1 & echo $! > /tmp/<job>.pid
161+
162+ Redirecting only stdout still blocks until the process exits. Read the
163+ `bg-jobs` skill before you wait on, inspect or stop such a job — each has a
164+ rule you cannot guess. Stop every job you started before you finish, and say
165+ which ones you left running.
166+
167+# Generation settings (OpenAI API keys). Low temperature for a coding agent:
168+# we want precise and reproducible answers, not creativity.
169+sampling:
170+ temperature: 0.0
171+ parallel_tool_calls: false
172+ top_p: 0.9
173+ max_tokens: 4096
174+
175+watchdogTimeout: 30s
176+
177+# Context compression
178+context:
179+ enabled: false
180+
181+ # Compress when the history reaches this share of the window.
182+ threshold: 75
183+
184+ # Fallback on the message count, for when the window is unknown.
185+ # One command costs 2 messages (call + response), so 80 is roughly 30 commands of history.
186+ maxMessages: 80
187+
188+ # How many recent questions (with their commands and answers) are kept as-is instead of being summarised.
189+ # The model works on the last few turns, so summarising them would make it redo what it just did.
190+ keepLastTurns: 3
191+
192+ # max_tokens of the summary request.
193+ summaryMaxTokens: 1200
194+
195+ # Replaces the built-in summary prompt when set.
196+ # Empty = built-in.
197+ prompt: ""
198+
199+ showStats: true
200+
201+displayCommands: true
new file mode 100644
@@ -0,0 +1,201 @@
1+# Agent settings — the LSP variant of agent.dmr.yaml.
2+#
3+# Same model and server as agent.dmr.yaml; the difference is the `lsp` block
4+# below (gopls on, the other servers ready to uncomment) and a system prompt
5+# that tells the model WHEN to ask the language server instead of grepping.
6+#
7+# mm agent.lsp.yaml # line mode
8+# mm -tui agent.lsp.yaml # window mode
9+#
10+provider: dmr
11+
12+model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M
13+baseUrl: http://localhost:12434/engines/v1
14+fallback: http://host.docker.internal:12434/engines/v1
15+
16+
17+# How many tokens the server actually serves.
18+# 0 = ask the server
19+contextWindow: 0
20+
21+# Max number of characters a tool returns to the model.
22+# Context safeguard: beyond that, the output is truncated (beginning + end kept).
23+maxOutput: 16000
24+
25+# Max number of model <-> tools round trips for a single question.
26+# Raise it for tasks that need long chains of commands.
27+maxTurns: 40
28+
29+# The `bash` tool.
30+# `false` removes it: the model can still read and edit files (editTools) and load skills,
31+# but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool".
32+bashTool: true
33+
34+# The built-in file tools: read_file, write_file, edit_file.
35+editTools: true
36+
37+# Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
38+# The file extension picks the server. A server missing from the PATH is skipped with a warning at start-up;
39+# with no server on the PATH the tool is not declared. Install what you use, uncomment its entry.
40+lsp:
41+ servers:
42+ - name: gopls # go install golang.org/x/tools/gopls@latest
43+ command: gopls
44+ extensions: [".go"]
45+ # - name: typescript # npm i -g typescript typescript-language-server
46+ # command: typescript-language-server
47+ # args: ["--stdio"]
48+ # extensions: [".ts", ".tsx", ".js", ".jsx"]
49+ # - name: python # pip install pyright (or: npm i -g pyright)
50+ # command: pyright-langserver
51+ # args: ["--stdio"]
52+ # extensions: [".py"]
53+ # - name: rust # rustup component add rust-analyzer
54+ # command: rust-analyzer
55+ # extensions: [".rs"]
56+ # - name: clangd # apt install clangd / brew install llvm
57+ # command: clangd
58+ # extensions: [".c", ".h", ".cc", ".cpp", ".hpp"]
59+ # - name: java # jdtls (Eclipse JDT language server), or another Java server
60+ # command: jdtls
61+ # extensions: [".java"]
62+
63+# Where the `read_skill` tool looks for its markdown procedures.
64+# The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills.
65+skillsDir: skills
66+
67+# Persistent sessions, per project.
68+# When enabled, every conversation is saved as one JSON file under `dir`
69+sessions:
70+ enabled: true
71+ dir: .mm/sessions
72+
73+# How many lines of a command's output are echoed to the terminal.
74+# 0 disables it.
75+previewLines: 20
76+
77+# The system prompt: what the agent is, and what it is allowed to do.
78+# This is the knob to play with — it is the shortest path to changing behaviour.
79+system: |
80+ Your name is Bob.
81+ You are a coding agent working in a terminal.
82+ You have a "bash" tool to run shell commands, file tools to read and edit,
83+ and an "lsp" tool to ask the language server about the code.
84+ Use bash to run tests, build, list and search; use lsp for anything about a symbol.
85+ Chain several commands if needed, then answer clearly in English.
86+
87+ A request often mixes things you answer from yourself ("say hello") with
88+ things only a command can answer ("list the files"). Handle every part, in
89+ the order asked, and run a command for each part that needs one.
90+ Never state the contents of a file, the output of a command, or the state of
91+ the repository unless a command in THIS answer returned it. What you did not
92+ read, you do not know: run the command instead of recalling it.
93+
94+ SKILLS
95+ You have a second tool, `read_skill`. Its description lists the procedures
96+ available for this project — one per kind of task.
97+
98+ Any request to DO something to a Go project is a skill, not a shell command
99+ you invent. Match the request against that list, call `read_skill` FIRST,
100+ before any bash command, and then follow what it says step by step.
101+
102+ FILE EDITING
103+ You have three tools for files: `read_file`, `edit_file` and `write_file`.
104+ They are how a file gets read and changed here: each change is exact,
105+ checked before it is written, and comes back as a diff with line numbers.
106+ bash is for running things — building, testing, listing, searching.
107+
108+ - Read before you write: call `read_file` on the file (numbered=true when
109+ you need line numbers). You cannot target text you have not seen; never
110+ rely on what you think you remember about a file.
111+ - To change an existing file, call `edit_file` with one or more {old, new}
112+ pairs. `old` is copied from the file character for character — same
113+ spaces, same indentation, same line breaks — and appears exactly once:
114+ add the surrounding lines until it is unique. Several pairs are applied
115+ together, against the original file. An empty `new` deletes the text.
116+ - Call `write_file` only to create a file, or to rewrite one entirely and
117+ on purpose. On an existing file it replaces everything, including what
118+ you did not intend to touch.
119+ - Read the diff the tool returns: it says exactly what changed and on which
120+ line. If `edit_file` refuses — text not found, ambiguous, overlapping
121+ edits — read the file again and fix `old`. Do not fall back to
122+ `write_file` to force the change through.
123+ - After editing code, run the narrowest check with bash: the formatter, the
124+ compiler, or the test covering that file.
125+
126+ CODE NAVIGATION (lsp)
127+ You have an `lsp` tool: it asks the project's language server what the
128+ compiler knows. Its description says which file extensions it covers.
129+ Use it INSTEAD of grep or reading whole files for anything about a symbol:
130+ - "where is X defined / declared" → action definition, symbol X
131+ - "who calls / uses X", "find usages" → action references, symbol X
132+ - "what is the type / signature of X" → action hover, symbol X
133+ - "what does this file contain" → action symbols, path only
134+ - "are there errors", after every edit → action diagnostics, path only
135+ - "rename X to Y everywhere" → action rename, symbol X, new_name Y
136+ Point at a symbol with its NAME (the `symbol` field), exactly as written in
137+ the code; add `line` only to pick one occurrence among several. Do not
138+ guess columns. Answers are "path:line:column <source line>": quote them,
139+ they are the evidence.
140+ Before changing a symbol used in several files, call references and say how
141+ many places are affected. After editing, call diagnostics on each edited
142+ file and fix what it reports before running tests. A `[lsp: …]` answer is
143+ information: "no language server for" means use bash; "not found in" means
144+ the name is not spelled as in the code — read the file and copy it.
145+
146+ RULES
147+ - Keep everything the file already does, unless the user asked to remove it.
148+ - Touch only the files the request is about. Do not add tests, files or
149+ features that were not asked for.
150+ - Never run a git command unless the user says git, commit or push.
151+ - Never move, rename or delete a file unless the user asked for it.
152+ - Then answer in English, in a few lines.
153+ - If you don't know how to use a <cli>, run `<cli> --help` (or `<cli> help`)
154+ to understand the options, then run the command.
155+
156+ BACKGROUND JOBS
157+ Never let a command block the answer. Anything that serves, watches or runs
158+ long goes to the background, with BOTH streams redirected and its pid kept:
159+
160+ nohup <command> > /tmp/<job>.log 2>&1 & echo $! > /tmp/<job>.pid
161+
162+ Redirecting only stdout still blocks until the process exits. Read the
163+ `bg-jobs` skill before you wait on, inspect or stop such a job — each has a
164+ rule you cannot guess. Stop every job you started before you finish, and say
165+ which ones you left running.
166+
167+# Generation settings (OpenAI API keys). Low temperature for a coding agent:
168+# we want precise and reproducible answers, not creativity.
169+sampling:
170+ temperature: 0.0
171+ parallel_tool_calls: false
172+ top_p: 0.9
173+ max_tokens: 4096
174+
175+watchdogTimeout: 30s
176+
177+# Context compression
178+context:
179+ enabled: false
180+
181+ # Compress when the history reaches this share of the window.
182+ threshold: 75
183+
184+ # Fallback on the message count, for when the window is unknown.
185+ # One command costs 2 messages (call + response), so 80 is roughly 30 commands of history.
186+ maxMessages: 80
187+
188+ # How many recent questions (with their commands and answers) are kept as-is instead of being summarised.
189+ # The model works on the last few turns, so summarising them would make it redo what it just did.
190+ keepLastTurns: 3
191+
192+ # max_tokens of the summary request.
193+ summaryMaxTokens: 1200
194+
195+ # Replaces the built-in summary prompt when set.
196+ # Empty = built-in.
197+ prompt: ""
198+
199+ showStats: true
200+
201+displayCommands: true
added demo/main.go +21 -0
new file mode 100644
@@ -0,0 +1,21 @@
1+package main
2+
3+import "fmt"
4+
5+// Human represents a person with a name
6+type Human struct {
7+ Name string
8+}
9+
10+// Hello returns a greeting message from the human
11+func (h Human) Hello() string {
12+ return fmt.Sprintf("Hello, %s!", h.Name)
13+}
14+
15+func main() {
16+ // Create a new human
17+ person := Human{Name: "World"}
18+
19+ // Print the greeting
20+ fmt.Println(person.Hello())
21+}
new file mode 100644
@@ -0,0 +1,21 @@
1+package main
2+
3+import "fmt"
4+
5+// Human represents a person with a name
6+type Human struct {
7+ Name string
8+}
9+
10+// Hello returns a greeting message from the human
11+func (h Human) Hello() string {
12+ return fmt.Sprintf("Hello, %s!", h.Name)
13+}
14+
15+func main() {
16+ // Create a new human
17+ person := Human{Name: "World"}
18+
19+ // Print the greeting
20+ fmt.Println(person.Hello())
21+}
modified docs/diagrams/packages.drawio +1 -1
@@ -1 +1 @@
1-<mxfile host="app.diagrams.net" modified="2026-09-15T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry"/></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry"/></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry"/></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry"/></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry"/></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry"/></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry"/></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry"/></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file
1+<mxfile host="app.diagrams.net" modified="2026-09-15T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry"/></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry"/></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry"/></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry"/></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry"/></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry"/></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry"/></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry"/></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry"/></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry"/></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file
@@ -1 +1 @@
1-<mxfile host="app.diagrams.net" modified="2026-09-15T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry"/></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry"/></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry"/></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry"/></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry"/></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry"/></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry"/></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry"/></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file\ No newline at end of file
1+<mxfile host="app.diagrams.net" modified="2026-09-15T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry"/></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry"/></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry"/></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry"/></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry"/></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry"/></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry"/></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry"/></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry"/></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry"/></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file\ No newline at end of file
modified docs/en/README.md +2 -0
@@ -18,6 +18,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
1818 - [Point the model at a file with `@path`](how-to/attach-a-file.md)
1919 - [Keep sessions and resume them](how-to/keep-sessions.md)
2020 - [Use the window mode (`mm -tui`)](how-to/use-the-window-mode.md)
21+- [Navigate and refactor code with a language server (`lsp`)](how-to/navigate-code-with-lsp.md)
2122
2223 ## Reference — look it up
2324
@@ -34,6 +35,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
3435 - [Providers: one implementation per protocol](explanation/providers.md)
3536 - [Context compression](explanation/context-compression.md)
3637 - [Persistent sessions](explanation/persistent-sessions.md) — files per project, why a start is a new session, `session/load` versus docker agent's `session/resume`
38+- [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
3739 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
3840
3941 ## Scope
@@ -18,6 +18,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
18 - [Point the model at a file with `@path`](how-to/attach-a-file.md)18 - [Point the model at a file with `@path`](how-to/attach-a-file.md)
19 - [Keep sessions and resume them](how-to/keep-sessions.md)19 - [Keep sessions and resume them](how-to/keep-sessions.md)
20 - [Use the window mode (`mm -tui`)](how-to/use-the-window-mode.md)20 - [Use the window mode (`mm -tui`)](how-to/use-the-window-mode.md)
21+- [Navigate and refactor code with a language server (`lsp`)](how-to/navigate-code-with-lsp.md)
21 22
22 ## Reference — look it up23 ## Reference — look it up
23 24
@@ -34,6 +35,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
34 - [Providers: one implementation per protocol](explanation/providers.md)35 - [Providers: one implementation per protocol](explanation/providers.md)
35 - [Context compression](explanation/context-compression.md)36 - [Context compression](explanation/context-compression.md)
36 - [Persistent sessions](explanation/persistent-sessions.md) — files per project, why a start is a new session, `session/load` versus docker agent's `session/resume`37 - [Persistent sessions](explanation/persistent-sessions.md) — files per project, why a start is a new session, `session/load` versus docker agent's `session/resume`
38+- [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
37 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)39 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
38 40
39 ## Scope41 ## Scope
modified docs/en/explanation/architecture.md +2 -1
@@ -12,6 +12,7 @@ The entry point, `main.go`, only does wiring: it loads the settings, opens the e
1212 | `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, and the provider registry. |
1313 | `tools` | The built-in tools the model can call. |
1414 | `fileedit` | Exact-replacement file editing, the rules behind `edit_file`. |
15+| `lsp` | The language-server client behind the `lsp` tool: starts the configured servers on first use (config `lsp.servers`), syncs files from disk, asks for definition, references, hover, symbols, diagnostics and rename, and shapes the answers as `path:line:column <line>` text. Built on `go.lsp.dev/protocol` and `go.lsp.dev/jsonrpc2`. |
1516 | `skills` | Discovery of markdown procedures and rendering of the `read_skill` catalogue. |
1617 | `command` | The one table of slash commands: name, description, which front ends offer it, whether it takes an argument. The REPL dispatches on it, the ACP façade advertises its Editor slice, the window's `/` picker lists it. |
1718 | `clipboard` | Puts text on the system clipboard: a native tool on `PATH` first (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 as the fallback, and it reports which. Used by `/copy` in the REPL and the window. |
@@ -45,7 +46,7 @@ A draw.io diagram of these packages and their dependencies is kept at [`docs/dia
4546
4647 **Turns are serialised in ACP mode.** The active sink is process-global and the engine was never built for concurrent generations, so a second `session/prompt` waits for the first to finish. The protocol leaves that choice to the agent, and editors do not send overlapping prompts anyway.
4748
48-**The loop detector is separate from the history.** It records tool, input and output triples; the same triple three times in a row makes the tool append an instruction to change approach. It is deliberately not reset by `/compact`: forgetting a conversation does not make a repeated command new.
49+**The loop detector is separate from the history.** It records tool, input and output triples; the same triple three times in a row makes the tool append an instruction to change approach. It is deliberately not reset by `/compact`: forgetting a conversation does not make a repeated command new. Advice is not enough for every model: on 2026-09-18 a 12B model repeated the same refused `edit_file` call thirty times, `[LOOP_DETECTED]` appended to each answer, until the user pressed Escape. So the detector has a second level: three flagged repeats after the threshold — six identical calls — and the tool returns a Go error instead of text, the one deliberate exception to "tools return text": the agent is stopping itself, not reporting a failure for the model to read. The engine shows the detector's sentence as the turn's error, does not retry the turn, and the session file records it. A third guard sits in the stream itself: the arguments of a tool call arrive delta by delta and none of it is shown, so a model that derails while composing one — `"Greet 2023-06-15 11:16:00 +0000 UTC 2023-06-15 …"` for four minutes, seen the same day — looks like nothing happening. The engine watches those deltas and stops the generation as soon as they repeat a pattern or pass a quarter megabyte.
4950
5051 ## Rejected alternatives
5152
@@ -12,6 +12,7 @@ The entry point, `main.go`, only does wiring: it loads the settings, opens the e
12 | `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, and the provider registry. |12 | `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, and the provider registry. |
13 | `tools` | The built-in tools the model can call. |13 | `tools` | The built-in tools the model can call. |
14 | `fileedit` | Exact-replacement file editing, the rules behind `edit_file`. |14 | `fileedit` | Exact-replacement file editing, the rules behind `edit_file`. |
15+| `lsp` | The language-server client behind the `lsp` tool: starts the configured servers on first use (config `lsp.servers`), syncs files from disk, asks for definition, references, hover, symbols, diagnostics and rename, and shapes the answers as `path:line:column <line>` text. Built on `go.lsp.dev/protocol` and `go.lsp.dev/jsonrpc2`. |
15 | `skills` | Discovery of markdown procedures and rendering of the `read_skill` catalogue. |16 | `skills` | Discovery of markdown procedures and rendering of the `read_skill` catalogue. |
16 | `command` | The one table of slash commands: name, description, which front ends offer it, whether it takes an argument. The REPL dispatches on it, the ACP façade advertises its Editor slice, the window's `/` picker lists it. |17 | `command` | The one table of slash commands: name, description, which front ends offer it, whether it takes an argument. The REPL dispatches on it, the ACP façade advertises its Editor slice, the window's `/` picker lists it. |
17 | `clipboard` | Puts text on the system clipboard: a native tool on `PATH` first (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 as the fallback, and it reports which. Used by `/copy` in the REPL and the window. |18 | `clipboard` | Puts text on the system clipboard: a native tool on `PATH` first (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 as the fallback, and it reports which. Used by `/copy` in the REPL and the window. |
@@ -45,7 +46,7 @@ A draw.io diagram of these packages and their dependencies is kept at [`docs/dia
45 46
46 **Turns are serialised in ACP mode.** The active sink is process-global and the engine was never built for concurrent generations, so a second `session/prompt` waits for the first to finish. The protocol leaves that choice to the agent, and editors do not send overlapping prompts anyway.47 **Turns are serialised in ACP mode.** The active sink is process-global and the engine was never built for concurrent generations, so a second `session/prompt` waits for the first to finish. The protocol leaves that choice to the agent, and editors do not send overlapping prompts anyway.
47 48
48-**The loop detector is separate from the history.** It records tool, input and output triples; the same triple three times in a row makes the tool append an instruction to change approach. It is deliberately not reset by `/compact`: forgetting a conversation does not make a repeated command new.49+**The loop detector is separate from the history.** It records tool, input and output triples; the same triple three times in a row makes the tool append an instruction to change approach. It is deliberately not reset by `/compact`: forgetting a conversation does not make a repeated command new. Advice is not enough for every model: on 2026-09-18 a 12B model repeated the same refused `edit_file` call thirty times, `[LOOP_DETECTED]` appended to each answer, until the user pressed Escape. So the detector has a second level: three flagged repeats after the threshold — six identical calls — and the tool returns a Go error instead of text, the one deliberate exception to "tools return text": the agent is stopping itself, not reporting a failure for the model to read. The engine shows the detector's sentence as the turn's error, does not retry the turn, and the session file records it. A third guard sits in the stream itself: the arguments of a tool call arrive delta by delta and none of it is shown, so a model that derails while composing one — `"Greet 2023-06-15 11:16:00 +0000 UTC 2023-06-15 …"` for four minutes, seen the same day — looks like nothing happening. The engine watches those deltas and stops the generation as soon as they repeat a pattern or pass a quarter megabyte.
49 50
50 ## Rejected alternatives51 ## Rejected alternatives
51 52
added docs/en/explanation/lsp-tool.md +39 -0
new file mode 100644
@@ -0,0 +1,39 @@
1+# The `lsp` tool: why one tool, and why a `symbol` field — explanation
2+
3+## What is this about?
4+
5+Until 2026-09-18 the model knew a project through `bash` (grep, ls, go build) and the file tools. That is how a human without an editor works, and it is slow and error-prone for the questions an editor answers instantly: where is this defined, who calls it, what type is this, what does the compiler say. Language servers answer those questions for every mainstream language over one protocol, the Language Server Protocol (LSP). Ticket 17 asked to give `mm` a client for it: configured servers, a built-in tool, documentation with system-prompt samples, and a warning rather than a failure when a server is missing.
6+
7+## Why one tool with an `action` field
8+
9+Six requests were wanted: definition, references, hover, document symbols, diagnostics, rename. They could have been six tools. They are one, `lsp`, with an `action` enum, for the model this agent targets — a 12B model that reads a tool list of five entries reliably and a list of eleven less so. One tool also carries one description, and the description is where the workflow lives: "references before you change a shared symbol, diagnostics after you edit". Six descriptions would have said it six times or not at all.
10+
11+## Why a `symbol` field, and positions tried in order
12+
13+LSP requests take a position: zero-based line and UTF-16 column. The first end-to-end run showed the model sending `column: 0` on every call — even when the question gave the column — and looping on the same error. Asking a small model to count characters is asking for a number it cannot produce. So the tool takes the identifier instead: `symbol: greet`. The client finds its whole-word occurrences in the file and tries them in order until the server answers something; occurrences on comment lines are tried last, because a name is usually mentioned in the comment above its declaration and the server has nothing to say about a comment. The second run, with `symbol`, answered the same question in two calls with the right lines. Exact `line` and `column` remain accepted, 1-based like `read_file numbered=true`, for the rare model that has them.
14+
15+The same idea governs the answers: every location comes back as `path:line:column <the source line>`. The model does not need to open the file to know what a hit is, so it does not — one call instead of three.
16+
17+## Why the files are synced from disk before every request
18+
19+An editor tells its server about every keystroke. `mm` has no keystrokes: the model edits with `edit_file`, and the file on disk is the truth. So before every request the client reads the file and sends its content — `didOpen` the first time, a whole-document `didChange` when it moved since — and the server always answers on what the model just wrote. Nothing else has to remember to notify anyone.
20+
21+## Why `rename` writes the files itself, and asks first
22+
23+A rename is a `WorkspaceEdit`: ranges to replace in several files. The client applies them to disk — from the end of each file backwards so earlier positions stay valid, with UTF-16 columns converted to bytes — then re-syncs the changed files so the next question is answered on the renamed code. In the terminal it runs like `edit_file` does; where a front end has a permission dialog (an editor over ACP, the window mode with `permissions: ask`) it asks, because it writes, while the five navigation actions do not: a dialog on every read teaches people to click "allow" without reading.
24+
25+## Why a missing server is a warning
26+
27+A configuration travels with a project; the machines it runs on differ. A server named in the YAML but absent from the `PATH` is reported once at start-up and left out of the tool's description; when none is found, the tool is not declared at all and the banner says so. The agent starts either way. Declaring a server the tool cannot start would have handed the model a tool that always fails — the silent-failure trap the whole project is built against.
28+
29+## Rejected alternatives
30+
31+- **One tool per action.** Rejected for the model's sake: a longer tool list, six descriptions, the workflow scattered.
32+- **A hand-rolled JSON-RPC client.** Considered, to keep the dependency count at zero. The user chose `go.lsp.dev/protocol` and `go.lsp.dev/jsonrpc2`: typed requests and results, the union types the protocol is full of already decoded. The price is two direct dependencies (plus `go.uber.org/zap`-free v1 packages' own few) and a Go 1.26 floor, which those modules require.
33+- **Letting the model count columns.** Rejected by evidence, see above.
34+- **Starting every configured server at launch.** Rejected: a Python server on a Go project would run for nothing. Servers start on the first call for a file they serve and stop when `mm` exits.
35+- **Feeding diagnostics to the model on its own.** Rejected: the model asks for them when the skill and the prompt say to (after an edit). Pushing them unasked would put text in the history the model did not request and cannot place.
36+
37+## How it relates to the rest
38+
39+`internal/lsp` is the client: server lifecycle, file sync, the six operations, the text shaping, the rename applied to disk. `internal/tools` declares the tool over it and decides what is printed or emitted as events, like the other tools. `internal/config` holds `lsp.servers` and refuses two servers on one extension. `main.go` checks the `PATH`, prints the warnings, and stops the servers at exit. The recap line gained a `🔎 N lsp call(s)` counter, kept apart from `⚙ commands` for the same reason `📝 file op(s)` is: asking the compiler is not running a command.
new file mode 100644
@@ -0,0 +1,39 @@
1+# The `lsp` tool: why one tool, and why a `symbol` field — explanation
2+
3+## What is this about?
4+
5+Until 2026-09-18 the model knew a project through `bash` (grep, ls, go build) and the file tools. That is how a human without an editor works, and it is slow and error-prone for the questions an editor answers instantly: where is this defined, who calls it, what type is this, what does the compiler say. Language servers answer those questions for every mainstream language over one protocol, the Language Server Protocol (LSP). Ticket 17 asked to give `mm` a client for it: configured servers, a built-in tool, documentation with system-prompt samples, and a warning rather than a failure when a server is missing.
6+
7+## Why one tool with an `action` field
8+
9+Six requests were wanted: definition, references, hover, document symbols, diagnostics, rename. They could have been six tools. They are one, `lsp`, with an `action` enum, for the model this agent targets — a 12B model that reads a tool list of five entries reliably and a list of eleven less so. One tool also carries one description, and the description is where the workflow lives: "references before you change a shared symbol, diagnostics after you edit". Six descriptions would have said it six times or not at all.
10+
11+## Why a `symbol` field, and positions tried in order
12+
13+LSP requests take a position: zero-based line and UTF-16 column. The first end-to-end run showed the model sending `column: 0` on every call — even when the question gave the column — and looping on the same error. Asking a small model to count characters is asking for a number it cannot produce. So the tool takes the identifier instead: `symbol: greet`. The client finds its whole-word occurrences in the file and tries them in order until the server answers something; occurrences on comment lines are tried last, because a name is usually mentioned in the comment above its declaration and the server has nothing to say about a comment. The second run, with `symbol`, answered the same question in two calls with the right lines. Exact `line` and `column` remain accepted, 1-based like `read_file numbered=true`, for the rare model that has them.
14+
15+The same idea governs the answers: every location comes back as `path:line:column <the source line>`. The model does not need to open the file to know what a hit is, so it does not — one call instead of three.
16+
17+## Why the files are synced from disk before every request
18+
19+An editor tells its server about every keystroke. `mm` has no keystrokes: the model edits with `edit_file`, and the file on disk is the truth. So before every request the client reads the file and sends its content — `didOpen` the first time, a whole-document `didChange` when it moved since — and the server always answers on what the model just wrote. Nothing else has to remember to notify anyone.
20+
21+## Why `rename` writes the files itself, and asks first
22+
23+A rename is a `WorkspaceEdit`: ranges to replace in several files. The client applies them to disk — from the end of each file backwards so earlier positions stay valid, with UTF-16 columns converted to bytes — then re-syncs the changed files so the next question is answered on the renamed code. In the terminal it runs like `edit_file` does; where a front end has a permission dialog (an editor over ACP, the window mode with `permissions: ask`) it asks, because it writes, while the five navigation actions do not: a dialog on every read teaches people to click "allow" without reading.
24+
25+## Why a missing server is a warning
26+
27+A configuration travels with a project; the machines it runs on differ. A server named in the YAML but absent from the `PATH` is reported once at start-up and left out of the tool's description; when none is found, the tool is not declared at all and the banner says so. The agent starts either way. Declaring a server the tool cannot start would have handed the model a tool that always fails — the silent-failure trap the whole project is built against.
28+
29+## Rejected alternatives
30+
31+- **One tool per action.** Rejected for the model's sake: a longer tool list, six descriptions, the workflow scattered.
32+- **A hand-rolled JSON-RPC client.** Considered, to keep the dependency count at zero. The user chose `go.lsp.dev/protocol` and `go.lsp.dev/jsonrpc2`: typed requests and results, the union types the protocol is full of already decoded. The price is two direct dependencies (plus `go.uber.org/zap`-free v1 packages' own few) and a Go 1.26 floor, which those modules require.
33+- **Letting the model count columns.** Rejected by evidence, see above.
34+- **Starting every configured server at launch.** Rejected: a Python server on a Go project would run for nothing. Servers start on the first call for a file they serve and stop when `mm` exits.
35+- **Feeding diagnostics to the model on its own.** Rejected: the model asks for them when the skill and the prompt say to (after an edit). Pushing them unasked would put text in the history the model did not request and cannot place.
36+
37+## How it relates to the rest
38+
39+`internal/lsp` is the client: server lifecycle, file sync, the six operations, the text shaping, the rename applied to disk. `internal/tools` declares the tool over it and decides what is printed or emitted as events, like the other tools. `internal/config` holds `lsp.servers` and refuses two servers on one extension. `main.go` checks the `PATH`, prints the warnings, and stops the servers at exit. The recap line gained a `🔎 N lsp call(s)` counter, kept apart from `⚙ commands` for the same reason `📝 file op(s)` is: asking the compiler is not running a command.
added docs/en/how-to/navigate-code-with-lsp.md +110 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/en/reference/configuration.md +30 -0
@@ -24,6 +24,7 @@
2424 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
2525 | `context` | map | see below | Automatic history compression. |
2626 | `sessions` | map | see below | Persistent, per-project sessions. |
27+| `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. |
2728 | `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. |
2829 | `permissions` | string | `auto` | `ask`: in the window mode, `bash`, `write_file` and `edit_file` wait for the user's answer (`a`/`s`/`r`) before running. `auto`: tools run at once. The line mode and the ACP façade do not read it (ACP has its own dialog). Must be `auto` or `ask`. |
2930
@@ -46,6 +47,30 @@
4647 | `sessions.enabled` | bool | `false` | Save every conversation to a file, rewritten after each turn, `/compact` and `/new`. The file also records, outside the history, every error shown (`errors` array: failed generation, failed compaction, failed `/resume`). Enables `/sessions` and `/resume` in the terminal and `session/load` over ACP. |
4748 | `sessions.dir` | string | `.mm/sessions` | Directory of the session files, one `<id>.json` each. Relative to the directory `mm` is started from (terminal) or to the session's `cwd` (ACP); an absolute path is used as-is. Must not be empty. |
4849
50+## `lsp` keys
51+
52+| Key | Type | Default | Description |
53+|-----|------|---------|-------------|
54+| `lsp.servers` | list | `[]` | One entry per language server. The file extension picks the server. |
55+| `lsp.servers[].name` | string | — | How the banner, the tool description and the warnings call it. Required. |
56+| `lsp.servers[].command` | string | — | The executable, looked up on the `PATH` at start-up. Required. Not found: warning, server skipped. |
57+| `lsp.servers[].args` | list of strings | `[]` | Its arguments (many servers need `--stdio`). |
58+| `lsp.servers[].extensions` | list of strings | — | File extensions it serves, with the dot (`.go`, `.ts`). Required, at least one; matched case-insensitively; an extension may be served by one server only. |
59+
60+```yaml
61+lsp:
62+ servers:
63+ - name: gopls
64+ command: gopls
65+ extensions: [".go"]
66+ - name: typescript
67+ command: typescript-language-server
68+ args: ["--stdio"]
69+ extensions: [".ts", ".tsx", ".js", ".jsx"]
70+```
71+
72+The shipped `agent.lsp.yaml` is `agent.dmr.yaml` with this block on (gopls active, other servers ready to uncomment) and a system prompt that says when to use the tool.
73+
4974 ## Provider defaults
5075
5176 | Provider | `baseUrl` | `fallback` | `apiKeyEnv` | Key required |
@@ -70,6 +95,11 @@ Reported as `[config error: <path>: <message>]`, exit code 1.
7095 | `context.summaryMaxTokens must be > 0` | zero or negative |
7196 | `context.maxMessages must be >= 0` | negative value |
7297 | `sessions.dir must not be empty` | empty string |
98+| `lsp.servers[N].name must not be empty` | a server without `name` |
99+| `lsp.servers[N].command must not be empty` | a server without `command` |
100+| `lsp.servers[N].extensions must list at least one extension` | a server without `extensions` |
101+| `lsp.servers[N].extensions: "go" must start with a dot, like ".go"` | an extension without its dot |
102+| `lsp.servers[N]: extension ".ts" is already served by "typescript"` | two servers on one extension |
73103 | `permissions must be "auto" or "ask"` | any other value |
74104 | `unknown provider "x" (known: dmr, llamacpp)` | Reported by the engine, as `[engine error: …]`. |
75105
@@ -24,6 +24,7 @@
24 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |24 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
25 | `context` | map | see below | Automatic history compression. |25 | `context` | map | see below | Automatic history compression. |
26 | `sessions` | map | see below | Persistent, per-project sessions. |26 | `sessions` | map | see below | Persistent, per-project sessions. |
27+| `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. |
27 | `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. |28 | `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. |
28 | `permissions` | string | `auto` | `ask`: in the window mode, `bash`, `write_file` and `edit_file` wait for the user's answer (`a`/`s`/`r`) before running. `auto`: tools run at once. The line mode and the ACP façade do not read it (ACP has its own dialog). Must be `auto` or `ask`. |29 | `permissions` | string | `auto` | `ask`: in the window mode, `bash`, `write_file` and `edit_file` wait for the user's answer (`a`/`s`/`r`) before running. `auto`: tools run at once. The line mode and the ACP façade do not read it (ACP has its own dialog). Must be `auto` or `ask`. |
29 30
@@ -46,6 +47,30 @@
46 | `sessions.enabled` | bool | `false` | Save every conversation to a file, rewritten after each turn, `/compact` and `/new`. The file also records, outside the history, every error shown (`errors` array: failed generation, failed compaction, failed `/resume`). Enables `/sessions` and `/resume` in the terminal and `session/load` over ACP. |47 | `sessions.enabled` | bool | `false` | Save every conversation to a file, rewritten after each turn, `/compact` and `/new`. The file also records, outside the history, every error shown (`errors` array: failed generation, failed compaction, failed `/resume`). Enables `/sessions` and `/resume` in the terminal and `session/load` over ACP. |
47 | `sessions.dir` | string | `.mm/sessions` | Directory of the session files, one `<id>.json` each. Relative to the directory `mm` is started from (terminal) or to the session's `cwd` (ACP); an absolute path is used as-is. Must not be empty. |48 | `sessions.dir` | string | `.mm/sessions` | Directory of the session files, one `<id>.json` each. Relative to the directory `mm` is started from (terminal) or to the session's `cwd` (ACP); an absolute path is used as-is. Must not be empty. |
48 49
50+## `lsp` keys
51+
52+| Key | Type | Default | Description |
53+|-----|------|---------|-------------|
54+| `lsp.servers` | list | `[]` | One entry per language server. The file extension picks the server. |
55+| `lsp.servers[].name` | string | — | How the banner, the tool description and the warnings call it. Required. |
56+| `lsp.servers[].command` | string | — | The executable, looked up on the `PATH` at start-up. Required. Not found: warning, server skipped. |
57+| `lsp.servers[].args` | list of strings | `[]` | Its arguments (many servers need `--stdio`). |
58+| `lsp.servers[].extensions` | list of strings | — | File extensions it serves, with the dot (`.go`, `.ts`). Required, at least one; matched case-insensitively; an extension may be served by one server only. |
59+
60+```yaml
61+lsp:
62+ servers:
63+ - name: gopls
64+ command: gopls
65+ extensions: [".go"]
66+ - name: typescript
67+ command: typescript-language-server
68+ args: ["--stdio"]
69+ extensions: [".ts", ".tsx", ".js", ".jsx"]
70+```
71+
72+The shipped `agent.lsp.yaml` is `agent.dmr.yaml` with this block on (gopls active, other servers ready to uncomment) and a system prompt that says when to use the tool.
73+
49 ## Provider defaults74 ## Provider defaults
50 75
51 | Provider | `baseUrl` | `fallback` | `apiKeyEnv` | Key required |76 | Provider | `baseUrl` | `fallback` | `apiKeyEnv` | Key required |
@@ -70,6 +95,11 @@ Reported as `[config error: <path>: <message>]`, exit code 1.
70 | `context.summaryMaxTokens must be > 0` | zero or negative |95 | `context.summaryMaxTokens must be > 0` | zero or negative |
71 | `context.maxMessages must be >= 0` | negative value |96 | `context.maxMessages must be >= 0` | negative value |
72 | `sessions.dir must not be empty` | empty string |97 | `sessions.dir must not be empty` | empty string |
98+| `lsp.servers[N].name must not be empty` | a server without `name` |
99+| `lsp.servers[N].command must not be empty` | a server without `command` |
100+| `lsp.servers[N].extensions must list at least one extension` | a server without `extensions` |
101+| `lsp.servers[N].extensions: "go" must start with a dot, like ".go"` | an extension without its dot |
102+| `lsp.servers[N]: extension ".ts" is already served by "typescript"` | two servers on one extension |
73 | `permissions must be "auto" or "ask"` | any other value |103 | `permissions must be "auto" or "ask"` | any other value |
74 | `unknown provider "x" (known: dmr, llamacpp)` | Reported by the engine, as `[engine error: …]`. |104 | `unknown provider "x" (known: dmr, llamacpp)` | Reported by the engine, as `[engine error: …]`. |
75 105
modified docs/en/reference/tools.md +44 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/en/tutorials/getting-started.md +1 -1
@@ -4,7 +4,7 @@ By the end of this tutorial, you will have built the `mm` binary, started it aga
44
55 ## Prerequisites
66
7-- Go 1.25 or later on your `PATH` (`go version` answers).
7+- Go 1.26 or later on your `PATH` (`go version` answers).
88 - Docker Desktop with Docker Model Runner enabled and reachable on `http://localhost:12434`.
99 - The model named in `agent.yaml` pulled locally. The file shipped with the project names `huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M`; pull it with `docker model pull <that name>`.
1010 - A terminal open at the root of the repository (the directory that holds `main.go` and `agent.yaml`).
@@ -4,7 +4,7 @@ By the end of this tutorial, you will have built the `mm` binary, started it aga
4 4
5 ## Prerequisites5 ## Prerequisites
6 6
7-- Go 1.25 or later on your `PATH` (`go version` answers).7+- Go 1.26 or later on your `PATH` (`go version` answers).
8 - Docker Desktop with Docker Model Runner enabled and reachable on `http://localhost:12434`.8 - Docker Desktop with Docker Model Runner enabled and reachable on `http://localhost:12434`.
9 - The model named in `agent.yaml` pulled locally. The file shipped with the project names `huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M`; pull it with `docker model pull <that name>`.9 - The model named in `agent.yaml` pulled locally. The file shipped with the project names `huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M`; pull it with `docker model pull <that name>`.
10 - A terminal open at the root of the repository (the directory that holds `main.go` and `agent.yaml`).10 - A terminal open at the root of the repository (the directory that holds `main.go` and `agent.yaml`).
modified docs/fr/README.md +2 -0
@@ -18,6 +18,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
1818 - [Désigner un fichier au modèle avec `@chemin`](how-to/attach-a-file.md)
1919 - [Conserver les sessions et les reprendre](how-to/keep-sessions.md)
2020 - [Utiliser le mode fenêtre (`mm -tui`)](how-to/use-the-window-mode.md)
21+- [Naviguer et refactorer le code avec un serveur de langage (`lsp`)](how-to/navigate-code-with-lsp.md)
2122
2223 ## Référence — consulter
2324
@@ -34,6 +35,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
3435 - [Fournisseurs : une implémentation par protocole](explanation/providers.md)
3536 - [Compression du contexte](explanation/context-compression.md)
3637 - [Les sessions persistantes](explanation/persistent-sessions.md) — des fichiers par projet, pourquoi un lancement est une nouvelle session, `session/load` face au `session/resume` de docker agent
38+- [L'outil `lsp`](explanation/lsp-tool.md) — un seul outil avec un champ `action`, un champ `symbol` plutôt que des colonnes, des fichiers synchronisés depuis le disque, un renommage soumis à permission
3739 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
3840
3941 ## Périmètre
@@ -18,6 +18,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
18 - [Désigner un fichier au modèle avec `@chemin`](how-to/attach-a-file.md)18 - [Désigner un fichier au modèle avec `@chemin`](how-to/attach-a-file.md)
19 - [Conserver les sessions et les reprendre](how-to/keep-sessions.md)19 - [Conserver les sessions et les reprendre](how-to/keep-sessions.md)
20 - [Utiliser le mode fenêtre (`mm -tui`)](how-to/use-the-window-mode.md)20 - [Utiliser le mode fenêtre (`mm -tui`)](how-to/use-the-window-mode.md)
21+- [Naviguer et refactorer le code avec un serveur de langage (`lsp`)](how-to/navigate-code-with-lsp.md)
21 22
22 ## Référence — consulter23 ## Référence — consulter
23 24
@@ -34,6 +35,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
34 - [Fournisseurs : une implémentation par protocole](explanation/providers.md)35 - [Fournisseurs : une implémentation par protocole](explanation/providers.md)
35 - [Compression du contexte](explanation/context-compression.md)36 - [Compression du contexte](explanation/context-compression.md)
36 - [Les sessions persistantes](explanation/persistent-sessions.md) — des fichiers par projet, pourquoi un lancement est une nouvelle session, `session/load` face au `session/resume` de docker agent37 - [Les sessions persistantes](explanation/persistent-sessions.md) — des fichiers par projet, pourquoi un lancement est une nouvelle session, `session/load` face au `session/resume` de docker agent
38+- [L'outil `lsp`](explanation/lsp-tool.md) — un seul outil avec un champ `action`, un champ `symbol` plutôt que des colonnes, des fichiers synchronisés depuis le disque, un renommage soumis à permission
37 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)39 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
38 40
39 ## Périmètre41 ## Périmètre
modified docs/fr/explanation/architecture.md +2 -1
@@ -12,6 +12,7 @@ Le point d'entrée, `main.go`, ne fait que du câblage : il charge les réglages
1212 | `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, et le registre des fournisseurs. |
1313 | `tools` | Les outils intégrés que le modèle peut appeler. |
1414 | `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. |
15+| `lsp` | Le client de serveur de langage derrière l'outil `lsp` : démarre les serveurs configurés au premier usage (clé `lsp.servers`), synchronise les fichiers depuis le disque, demande définition, références, hover, symboles, diagnostics et renommage, et met les réponses en forme `chemin:ligne:colonne <ligne>`. Construit sur `go.lsp.dev/protocol` et `go.lsp.dev/jsonrpc2`. |
1516 | `skills` | La découverte des procédures markdown et le rendu du catalogue de `read_skill`. |
1617 | `command` | L'unique table des commandes slash : nom, description, façades qui l'offrent, argument éventuel. Le REPL s'y branche, la façade ACP en annonce la tranche Éditeur, le sélecteur `/` de la fenêtre la liste. |
1718 | `clipboard` | Met du texte dans le presse-papiers système : un outil natif du `PATH` d'abord (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 en repli, et il dit lequel a servi. Utilisé par `/copy` dans le REPL et la fenêtre. |
@@ -45,7 +46,7 @@ Un diagramme draw.io de ces paquets et de leurs dépendances est conservé dans
4546
4647 **Les tours sont sérialisés en mode ACP.** Le puits actif est global au processus et le moteur n'a jamais été conçu pour des générations concurrentes, donc un second `session/prompt` attend la fin du premier. Le protocole laisse ce choix à l'agent, et les éditeurs n'envoient de toute façon pas de prompts qui se chevauchent.
4748
48-**Le détecteur de boucle est séparé de l'historique.** Il enregistre des triplets outil, entrée, sortie ; le même triplet trois fois de suite fait ajouter par l'outil une instruction de changer d'approche. Il n'est volontairement pas remis à zéro par `/compact` : oublier une conversation ne rend pas nouvelle une commande répétée.
49+**Le détecteur de boucle est séparé de l'historique.** Il enregistre des triplets outil, entrée, sortie ; le même triplet trois fois de suite fait ajouter par l'outil une instruction de changer d'approche. Il n'est volontairement pas remis à zéro par `/compact` : oublier une conversation ne rend pas nouvelle une commande répétée. Le conseil ne suffit pas à tous les modèles : le 18 septembre 2026, un modèle de 12 milliards de paramètres a répété trente fois le même `edit_file` refusé, `[LOOP_DETECTED]` ajouté à chaque réponse, jusqu'à ce que l'utilisateur appuie sur Échap. Le détecteur a donc un second niveau : trois répétitions signalées après le seuil — six appels identiques — et l'outil renvoie une erreur Go au lieu de texte, la seule exception délibérée à « les outils répondent en texte » : l'agent s'arrête lui-même, il ne rapporte pas un échec à lire par le modèle. Le moteur affiche la phrase du détecteur comme erreur du tour, ne rejoue pas le tour, et le fichier de session l'enregistre. Un troisième garde-fou est dans le flux lui-même : les arguments d'un appel d'outil arrivent fragment par fragment et rien n'en est affiché, donc un modèle qui déraille en composant un appel — `"Greet 2023-06-15 11:16:00 +0000 UTC 2023-06-15 …"` pendant quatre minutes, vu le même jour — ressemble à « rien ne se passe ». Le moteur surveille ces fragments et arrête la génération dès qu'ils répètent un motif ou dépassent un quart de mégaoctet.
4950
5051 ## Alternatives écartées
5152
@@ -12,6 +12,7 @@ Le point d'entrée, `main.go`, ne fait que du câblage : il charge les réglages
12 | `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, et le registre des fournisseurs. |12 | `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, et le registre des fournisseurs. |
13 | `tools` | Les outils intégrés que le modèle peut appeler. |13 | `tools` | Les outils intégrés que le modèle peut appeler. |
14 | `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. |14 | `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. |
15+| `lsp` | Le client de serveur de langage derrière l'outil `lsp` : démarre les serveurs configurés au premier usage (clé `lsp.servers`), synchronise les fichiers depuis le disque, demande définition, références, hover, symboles, diagnostics et renommage, et met les réponses en forme `chemin:ligne:colonne <ligne>`. Construit sur `go.lsp.dev/protocol` et `go.lsp.dev/jsonrpc2`. |
15 | `skills` | La découverte des procédures markdown et le rendu du catalogue de `read_skill`. |16 | `skills` | La découverte des procédures markdown et le rendu du catalogue de `read_skill`. |
16 | `command` | L'unique table des commandes slash : nom, description, façades qui l'offrent, argument éventuel. Le REPL s'y branche, la façade ACP en annonce la tranche Éditeur, le sélecteur `/` de la fenêtre la liste. |17 | `command` | L'unique table des commandes slash : nom, description, façades qui l'offrent, argument éventuel. Le REPL s'y branche, la façade ACP en annonce la tranche Éditeur, le sélecteur `/` de la fenêtre la liste. |
17 | `clipboard` | Met du texte dans le presse-papiers système : un outil natif du `PATH` d'abord (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 en repli, et il dit lequel a servi. Utilisé par `/copy` dans le REPL et la fenêtre. |18 | `clipboard` | Met du texte dans le presse-papiers système : un outil natif du `PATH` d'abord (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 en repli, et il dit lequel a servi. Utilisé par `/copy` dans le REPL et la fenêtre. |
@@ -45,7 +46,7 @@ Un diagramme draw.io de ces paquets et de leurs dépendances est conservé dans
45 46
46 **Les tours sont sérialisés en mode ACP.** Le puits actif est global au processus et le moteur n'a jamais été conçu pour des générations concurrentes, donc un second `session/prompt` attend la fin du premier. Le protocole laisse ce choix à l'agent, et les éditeurs n'envoient de toute façon pas de prompts qui se chevauchent.47 **Les tours sont sérialisés en mode ACP.** Le puits actif est global au processus et le moteur n'a jamais été conçu pour des générations concurrentes, donc un second `session/prompt` attend la fin du premier. Le protocole laisse ce choix à l'agent, et les éditeurs n'envoient de toute façon pas de prompts qui se chevauchent.
47 48
48-**Le détecteur de boucle est séparé de l'historique.** Il enregistre des triplets outil, entrée, sortie ; le même triplet trois fois de suite fait ajouter par l'outil une instruction de changer d'approche. Il n'est volontairement pas remis à zéro par `/compact` : oublier une conversation ne rend pas nouvelle une commande répétée.49+**Le détecteur de boucle est séparé de l'historique.** Il enregistre des triplets outil, entrée, sortie ; le même triplet trois fois de suite fait ajouter par l'outil une instruction de changer d'approche. Il n'est volontairement pas remis à zéro par `/compact` : oublier une conversation ne rend pas nouvelle une commande répétée. Le conseil ne suffit pas à tous les modèles : le 18 septembre 2026, un modèle de 12 milliards de paramètres a répété trente fois le même `edit_file` refusé, `[LOOP_DETECTED]` ajouté à chaque réponse, jusqu'à ce que l'utilisateur appuie sur Échap. Le détecteur a donc un second niveau : trois répétitions signalées après le seuil — six appels identiques — et l'outil renvoie une erreur Go au lieu de texte, la seule exception délibérée à « les outils répondent en texte » : l'agent s'arrête lui-même, il ne rapporte pas un échec à lire par le modèle. Le moteur affiche la phrase du détecteur comme erreur du tour, ne rejoue pas le tour, et le fichier de session l'enregistre. Un troisième garde-fou est dans le flux lui-même : les arguments d'un appel d'outil arrivent fragment par fragment et rien n'en est affiché, donc un modèle qui déraille en composant un appel — `"Greet 2023-06-15 11:16:00 +0000 UTC 2023-06-15 …"` pendant quatre minutes, vu le même jour — ressemble à « rien ne se passe ». Le moteur surveille ces fragments et arrête la génération dès qu'ils répètent un motif ou dépassent un quart de mégaoctet.
49 50
50 ## Alternatives écartées51 ## Alternatives écartées
51 52
added docs/fr/explanation/lsp-tool.md +39 -0
Large diff collapsed to keep the page fast — load it when you need it.
added docs/fr/how-to/navigate-code-with-lsp.md +110 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/reference/configuration.md +30 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/reference/tools.md +44 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/tutorials/getting-started.md +1 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified go.mod +9 -5
Large diff collapsed to keep the page fast — load it when you need it.
modified go.sum +20 -12
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/acp/acp.go +2 -2
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/agent/agent.go +3 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/config/config.go +96 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/config/config_test.go +55 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/detector/detector.go +61 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/detector/detector_test.go +45 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/engine/engine.go +32 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/engine/engine_test.go +53 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/engine/explain.go +21 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/engine/runaway.go +108 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/engine/runaway_test.go +81 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/fileedit/edits.go +19 -2
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/fileedit/fileedit_test.go +1 -1
Large diff collapsed to keep the page fast — load it when you need it.
added internal/lsp/change.go +159 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/lsp/edit.go +167 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/lsp/format.go +152 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/lsp/locate.go +110 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/lsp/lsp.go +418 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/lsp/lsp_test.go +343 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/lsp/ops.go +201 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/tools/files.go +27 -22
Large diff collapsed to keep the page fast — load it when you need it.
added internal/tools/lsp.go +293 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/tools/lsp_test.go +183 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/tools/tools.go +9 -22
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/tui/turn.go +3 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified main.go +65 -18
Large diff collapsed to keep the page fast — load it when you need it.
added skills/lsp-navigation/SKILL.md +45 -0
Large diff collapsed to keep the page fast — load it when you need it.