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

Merge feature/last-question into main

rickub committed 2026-10-20T05:40:46Z Browse files
5340d59 parents: 85e8e1f 65bdebd
modified .tickets/issues/0068-recall-the-last-commands-last.yaml +127 -5
@@ -1,14 +1,136 @@
11 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
22 id: 68
33 title: Recall the last commands (/last)
4-state: open
4+state: closed
55 author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-10-02T07:01:13.955Z
9-updatedAt: 2026-10-06T13:14:55.214Z
9+updatedAt: 2026-10-20T11:00:00.000Z
1010 labels: []
11+milestone: v0.8.0
1112 epic: Improve TUI
12-body: ""
13-tasks: []
14-comments: []
13+body: |
14+ in --tui mode, with the arrows up and down, recall the last user questions if
15+ ```
16+ sessions:
17+ enabled: true
18+ ```
19+
20+ propose something to clear the question, if the user wants to type something else
21+tasks:
22+ - id: 1
23+ title: "Agree on the source of the questions, the keys and how to clear the box"
24+ state: closed
25+ priority: none
26+ author:
27+ name: k33g
28+ email: ph.charriere@gmail.com
29+ createdAt: 2026-10-20T08:00:00.000Z
30+ updatedAt: 2026-10-20T08:00:00.000Z
31+ - id: 2
32+ title: "store: Keeper.Questions(current) — user messages, current session first then earlier sessions newest first, summaries and mm notes skipped, consecutive duplicates dropped, capped — with tests"
33+ state: closed
34+ priority: none
35+ author:
36+ name: k33g
37+ email: ph.charriere@gmail.com
38+ createdAt: 2026-10-20T08:00:00.000Z
39+ updatedAt: 2026-10-20T08:00:00.000Z
40+ - id: 3
41+ title: "tui: up on the first row of the box recalls the previous question, down on the last row the next one, then the draft; scrolling unchanged when nothing to recall — with tests"
42+ state: closed
43+ priority: none
44+ author:
45+ name: k33g
46+ email: ph.charriere@gmail.com
47+ createdAt: 2026-10-20T08:00:00.000Z
48+ updatedAt: 2026-10-20T08:00:00.000Z
49+ - id: 4
50+ title: "tui: Escape when idle with text in the box clears it — with tests"
51+ state: closed
52+ priority: none
53+ author:
54+ name: k33g
55+ email: ph.charriere@gmail.com
56+ createdAt: 2026-10-20T08:00:00.000Z
57+ updatedAt: 2026-10-20T08:00:00.000Z
58+ - id: 5
59+ title: "Quality gate, docs EN+FR (reference keys, how-to sessions), package READMEs, .memory"
60+ state: closed
61+ priority: none
62+ author:
63+ name: k33g
64+ email: ph.charriere@gmail.com
65+ createdAt: 2026-10-20T08:00:00.000Z
66+ updatedAt: 2026-10-20T08:00:00.000Z
67+comments:
68+ - id: 1
69+ author:
70+ name: k33g
71+ email: ph.charriere@gmail.com
72+ createdAt: 2026-10-20T08:00:00.000Z
73+ body: |
74+ Decided by the user (2026-10-20):
75+ - source: the user messages already stored in the session files (no new
76+ field) — so a recalled question carries what mm stored with it
77+ ([attached file: …] lines, pastes in full); slash commands are not in
78+ the files and are not recalled;
79+ - keys: up/down in the box (up on its first row, down on its last); the
80+ conversation still scrolls with PgUp/PgDn, the wheel, and the arrows
81+ after Tab;
82+ - clearing: Escape when no turn runs and the box is not empty clears it
83+ (Escape during a turn still cancels it); down past the newest question
84+ also gives back the draft.
85+ Branch feature/last-question (created by the user).
86+ - id: 2
87+ author:
88+ name: k33g
89+ email: ph.charriere@gmail.com
90+ createdAt: 2026-10-20T09:00:00.000Z
91+ body: |
92+ Tasks 2-4 done on feature/last-question (go test ./... green):
93+ - store/questions.go: Keeper.Questions(current) — newest first, the
94+ conversation in memory then the other session files (Store.all, which
95+ List now uses too); skips /compact summaries, mm's notes
96+ (engine.IsNote: "[mm: …" retry notes, "[prompt blocked by a hook…"),
97+ empty texts, what a preGeneration hook appended (cut at
98+ hooks.ContextLabel); consecutive duplicates once; capped at 200.
99+ - tui/recall.go: ↑ on the box's first row / ↓ on its last; fetched once
100+ per walk (hooks.questions → runner.questions); ↓ past the newest gives
101+ back the draft (pastes included); editing ends the walk; nothing to
102+ recall → the arrows scroll as before; a recalled "@…" does not open
103+ the picker.
104+ - Found while testing: a turn that fails or is cancelled before
105+ answering drops its question from the history (session.AfterTurn),
106+ so the stored-messages source alone could not recall it — the one you
107+ most want to retry. The runner keeps it (runner.asked) until the next
108+ question; it is in memory only, not in the session file.
109+ - Escape: idle with text in the box → clears it (and the walk); during a
110+ turn → cancels as before; empty idle box → the old notice. F1 help
111+ line updated.
112+ - id: 3
113+ author:
114+ name: k33g
115+ email: ph.charriere@gmail.com
116+ createdAt: 2026-10-20T10:00:00.000Z
117+ body: |
118+ Docs EN+FR: how-to/use-the-window-mode (new section "Recall an earlier
119+ question"), how-to/keep-sessions (variant), reference/slash-commands
120+ (Esc and ↑↓ rows), explanation/persistent-sessions (why the stored
121+ questions, the failed-turn gap, the rejected "inputs" field);
122+ docs/diagrams/packages.drawio: store's import edges (it had none).
123+ Live check: mm -tui driven through a pty, no model server, one stored
124+ session → ↑ gave its last question, ↑ the one before, Esc cleared the
125+ box, Esc on the empty box gave "No generation in progress".
126+ Task 5 stays open: the qlty quality gate was NOT run (qlty is not in this
127+ sandbox — kits/qlty needed); gofmt and go vet clean, go test ./... green.
128+ - id: 4
129+ author:
130+ name: k33g
131+ email: ph.charriere@gmail.com
132+ createdAt: 2026-10-20T11:00:00.000Z
133+ body: |
134+ Closed at the user's request ("c'est ok"). Task 5 closed with the qlty
135+ quality gate still NOT run (qlty absent from the sandbox) — to run with
136+ kits/qlty at the next quality pass.
@@ -1,14 +1,136 @@
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: 682 id: 68
3 title: Recall the last commands (/last)3 title: Recall the last commands (/last)
4-state: open4+state: closed
5 author:5 author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-10-02T07:01:13.955Z8 createdAt: 2026-10-02T07:01:13.955Z
9-updatedAt: 2026-10-06T13:14:55.214Z9+updatedAt: 2026-10-20T11:00:00.000Z
10 labels: []10 labels: []
11+milestone: v0.8.0
11 epic: Improve TUI12 epic: Improve TUI
12-body: ""13+body: |
13-tasks: []14+ in --tui mode, with the arrows up and down, recall the last user questions if
14-comments: []15+ ```
16+ sessions:
17+ enabled: true
18+ ```
19+
20+ propose something to clear the question, if the user wants to type something else
21+tasks:
22+ - id: 1
23+ title: "Agree on the source of the questions, the keys and how to clear the box"
24+ state: closed
25+ priority: none
26+ author:
27+ name: k33g
28+ email: ph.charriere@gmail.com
29+ createdAt: 2026-10-20T08:00:00.000Z
30+ updatedAt: 2026-10-20T08:00:00.000Z
31+ - id: 2
32+ title: "store: Keeper.Questions(current) — user messages, current session first then earlier sessions newest first, summaries and mm notes skipped, consecutive duplicates dropped, capped — with tests"
33+ state: closed
34+ priority: none
35+ author:
36+ name: k33g
37+ email: ph.charriere@gmail.com
38+ createdAt: 2026-10-20T08:00:00.000Z
39+ updatedAt: 2026-10-20T08:00:00.000Z
40+ - id: 3
41+ title: "tui: up on the first row of the box recalls the previous question, down on the last row the next one, then the draft; scrolling unchanged when nothing to recall — with tests"
42+ state: closed
43+ priority: none
44+ author:
45+ name: k33g
46+ email: ph.charriere@gmail.com
47+ createdAt: 2026-10-20T08:00:00.000Z
48+ updatedAt: 2026-10-20T08:00:00.000Z
49+ - id: 4
50+ title: "tui: Escape when idle with text in the box clears it — with tests"
51+ state: closed
52+ priority: none
53+ author:
54+ name: k33g
55+ email: ph.charriere@gmail.com
56+ createdAt: 2026-10-20T08:00:00.000Z
57+ updatedAt: 2026-10-20T08:00:00.000Z
58+ - id: 5
59+ title: "Quality gate, docs EN+FR (reference keys, how-to sessions), package READMEs, .memory"
60+ state: closed
61+ priority: none
62+ author:
63+ name: k33g
64+ email: ph.charriere@gmail.com
65+ createdAt: 2026-10-20T08:00:00.000Z
66+ updatedAt: 2026-10-20T08:00:00.000Z
67+comments:
68+ - id: 1
69+ author:
70+ name: k33g
71+ email: ph.charriere@gmail.com
72+ createdAt: 2026-10-20T08:00:00.000Z
73+ body: |
74+ Decided by the user (2026-10-20):
75+ - source: the user messages already stored in the session files (no new
76+ field) — so a recalled question carries what mm stored with it
77+ ([attached file: …] lines, pastes in full); slash commands are not in
78+ the files and are not recalled;
79+ - keys: up/down in the box (up on its first row, down on its last); the
80+ conversation still scrolls with PgUp/PgDn, the wheel, and the arrows
81+ after Tab;
82+ - clearing: Escape when no turn runs and the box is not empty clears it
83+ (Escape during a turn still cancels it); down past the newest question
84+ also gives back the draft.
85+ Branch feature/last-question (created by the user).
86+ - id: 2
87+ author:
88+ name: k33g
89+ email: ph.charriere@gmail.com
90+ createdAt: 2026-10-20T09:00:00.000Z
91+ body: |
92+ Tasks 2-4 done on feature/last-question (go test ./... green):
93+ - store/questions.go: Keeper.Questions(current) — newest first, the
94+ conversation in memory then the other session files (Store.all, which
95+ List now uses too); skips /compact summaries, mm's notes
96+ (engine.IsNote: "[mm: …" retry notes, "[prompt blocked by a hook…"),
97+ empty texts, what a preGeneration hook appended (cut at
98+ hooks.ContextLabel); consecutive duplicates once; capped at 200.
99+ - tui/recall.go: ↑ on the box's first row / ↓ on its last; fetched once
100+ per walk (hooks.questions → runner.questions); ↓ past the newest gives
101+ back the draft (pastes included); editing ends the walk; nothing to
102+ recall → the arrows scroll as before; a recalled "@…" does not open
103+ the picker.
104+ - Found while testing: a turn that fails or is cancelled before
105+ answering drops its question from the history (session.AfterTurn),
106+ so the stored-messages source alone could not recall it — the one you
107+ most want to retry. The runner keeps it (runner.asked) until the next
108+ question; it is in memory only, not in the session file.
109+ - Escape: idle with text in the box → clears it (and the walk); during a
110+ turn → cancels as before; empty idle box → the old notice. F1 help
111+ line updated.
112+ - id: 3
113+ author:
114+ name: k33g
115+ email: ph.charriere@gmail.com
116+ createdAt: 2026-10-20T10:00:00.000Z
117+ body: |
118+ Docs EN+FR: how-to/use-the-window-mode (new section "Recall an earlier
119+ question"), how-to/keep-sessions (variant), reference/slash-commands
120+ (Esc and ↑↓ rows), explanation/persistent-sessions (why the stored
121+ questions, the failed-turn gap, the rejected "inputs" field);
122+ docs/diagrams/packages.drawio: store's import edges (it had none).
123+ Live check: mm -tui driven through a pty, no model server, one stored
124+ session → ↑ gave its last question, ↑ the one before, Esc cleared the
125+ box, Esc on the empty box gave "No generation in progress".
126+ Task 5 stays open: the qlty quality gate was NOT run (qlty is not in this
127+ sandbox — kits/qlty needed); gofmt and go vet clean, go test ./... green.
128+ - id: 4
129+ author:
130+ name: k33g
131+ email: ph.charriere@gmail.com
132+ createdAt: 2026-10-20T11:00:00.000Z
133+ body: |
134+ Closed at the user's request ("c'est ok"). Task 5 closed with the qlty
135+ quality gate still NOT run (qlty absent from the sandbox) — to run with
136+ kits/qlty at the next quality pass.
modified .tickets/issues/0089-autonomous-agent.yaml +1 -2
@@ -6,9 +6,8 @@ author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-10-16T15:36:08.133Z
9-updatedAt: 2026-10-18T15:16:03.149Z
9+updatedAt: 2026-10-20T05:36:48.607Z
1010 labels: []
11-milestone: v0.8.0
1211 related:
1312 - 90
1413 body: |
@@ -6,9 +6,8 @@ author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-10-16T15:36:08.133Z8 createdAt: 2026-10-16T15:36:08.133Z
9-updatedAt: 2026-10-18T15:16:03.149Z9+updatedAt: 2026-10-20T05:36:48.607Z
10 labels: []10 labels: []
11-milestone: v0.8.0
12 related:11 related:
13 - 9012 - 90
14 body: |13 body: |
modified .tickets/issues/0090-agent-identity.yaml +1 -2
@@ -6,9 +6,8 @@ author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-10-16T15:37:32.062Z
9-updatedAt: 2026-10-18T15:15:45.705Z
9+updatedAt: 2026-10-20T05:37:03.730Z
1010 labels: []
11-milestone: v0.8.0
1211 body: |
1312 Des idées pour gérer cela ?
1413 avec l'existant (est ce que un seul useur peut avoir plusieurs identités sur GitHub par exemple)
@@ -6,9 +6,8 @@ author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-10-16T15:37:32.062Z8 createdAt: 2026-10-16T15:37:32.062Z
9-updatedAt: 2026-10-18T15:15:45.705Z9+updatedAt: 2026-10-20T05:37:03.730Z
10 labels: []10 labels: []
11-milestone: v0.8.0
12 body: |11 body: |
13 Des idées pour gérer cela ?12 Des idées pour gérer cela ?
14 avec l'existant (est ce que un seul useur peut avoir plusieurs identités sur GitHub par exemple)13 avec l'existant (est ce que un seul useur peut avoir plusieurs identités sur GitHub par exemple)
modified .tickets/issues/0096-in-guides-add-a-tips-document-eg-how-to-resume-a.yaml +1 -2
@@ -6,11 +6,10 @@ author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-10-18T09:26:47.068Z
9-updatedAt: 2026-10-18T13:49:23.480Z
9+updatedAt: 2026-10-20T05:37:26.481Z
1010 labels:
1111 - priority::medium
1212 - documentation
13-milestone: v0.8.0
1413 body: |
1514 example: when the agent (or the model) is "stuck"...
1615 tasks: []
@@ -6,11 +6,10 @@ author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-10-18T09:26:47.068Z8 createdAt: 2026-10-18T09:26:47.068Z
9-updatedAt: 2026-10-18T13:49:23.480Z9+updatedAt: 2026-10-20T05:37:26.481Z
10 labels:10 labels:
11 - priority::medium11 - priority::medium
12 - documentation12 - documentation
13-milestone: v0.8.0
14 body: |13 body: |
15 example: when the agent (or the model) is "stuck"...14 example: when the agent (or the model) is "stuck"...
16 tasks: []15 tasks: []
modified .tickets/issues/0097-generate-an-agent-yaml-file-with-rhe-cli.yaml +2 -2
@@ -6,9 +6,9 @@ author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-10-18T12:31:14.544Z
9-updatedAt: 2026-10-18T13:26:23.314Z
9+updatedAt: 2026-10-20T05:38:52.918Z
1010 labels: []
11-milestone: v0.8.0
11+milestone: v0.9.0
1212 related:
1313 - 98
1414 body: ""
@@ -6,9 +6,9 @@ author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-10-18T12:31:14.544Z8 createdAt: 2026-10-18T12:31:14.544Z
9-updatedAt: 2026-10-18T13:26:23.314Z9+updatedAt: 2026-10-20T05:38:52.918Z
10 labels: []10 labels: []
11-milestone: v0.8.011+milestone: v0.9.0
12 related:12 related:
13 - 9813 - 98
14 body: ""14 body: ""
modified .tickets/milestones.yaml +4 -2
@@ -22,11 +22,13 @@ milestones:
2222 state: closed
2323 dueOn: 2026-10-28
2424 - title: v0.6.0
25- state: open
25+ state: closed
2626 dueOn: 2026-10-28
2727 - title: v0.7.0
28- state: open
28+ state: closed
2929 dueOn: 2026-10-28
3030 - title: v0.8.0
3131 state: open
3232 dueOn: 2026-10-28
33+ - title: v0.9.0
34+ state: open
@@ -22,11 +22,13 @@ milestones:
22 state: closed22 state: closed
23 dueOn: 2026-10-2823 dueOn: 2026-10-28
24 - title: v0.6.024 - title: v0.6.0
25- state: open25+ state: closed
26 dueOn: 2026-10-2826 dueOn: 2026-10-28
27 - title: v0.7.027 - title: v0.7.0
28- state: open28+ state: closed
29 dueOn: 2026-10-2829 dueOn: 2026-10-28
30 - title: v0.8.030 - title: v0.8.0
31 state: open31 state: open
32 dueOn: 2026-10-2832 dueOn: 2026-10-28
33+ - title: v0.9.0
34+ state: open
modified docs/diagrams/packages.drawio +1 -1
@@ -1,3 +1,3 @@
11 <mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content; declared toolsbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;lt;/fontbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell>
22 <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell>
3- <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="saved" value="&lt;b&gt;internal/saved&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;an answer to a new file: /save, save_answer&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e87" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e88" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e89" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="&lt;b&gt;internal/structured&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;structuredOutput: Go declarations → JSON Schema (go/parser)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="&lt;b&gt;internal/agentsmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the project's AGENTS.md, appended to the system prompt per conversation&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="&lt;b&gt;internal/elicit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ask_user's questions and answers: normalize, line answers, elicitation form schema&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="&lt;b&gt;internal/hooks&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_main_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file
3+ <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json), the questions ↑ recalls&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="saved" value="&lt;b&gt;internal/saved&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;an answer to a new file: /save, save_answer&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e87" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e88" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e89" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="&lt;b&gt;internal/structured&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;structuredOutput: Go declarations → JSON Schema (go/parser)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="&lt;b&gt;internal/agentsmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the project's AGENTS.md, appended to the system prompt per conversation&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="&lt;b&gt;internal/elicit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ask_user's questions and answers: normalize, line answers, elicitation form schema&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="&lt;b&gt;internal/hooks&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_main_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_session" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_engine" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file
@@ -1,3 +1,3 @@
1 <mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content; declared toolsbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;lt;/fontbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell>1 <mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content; declared toolsbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;lt;/fontbuilt-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell>
2 <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell>2 <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell>
3- <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="saved" value="&lt;b&gt;internal/saved&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;an answer to a new file: /save, save_answer&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e87" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e88" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e89" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="&lt;b&gt;internal/structured&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;structuredOutput: Go declarations → JSON Schema (go/parser)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="&lt;b&gt;internal/agentsmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the project's AGENTS.md, appended to the system prompt per conversation&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="&lt;b&gt;internal/elicit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ask_user's questions and answers: normalize, line answers, elicitation form schema&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="&lt;b&gt;internal/hooks&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_main_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file\ No newline at end of file
3+ <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json), the questions ↑ recalls&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="saved" value="&lt;b&gt;internal/saved&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;an answer to a new file: /save, save_answer&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e87" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e88" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e89" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="saved"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="&lt;b&gt;internal/ask&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;/ask another mm agent: child mm -acp, ACP client side&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="&lt;b&gt;internal/consent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;yolo switch, the four answers, per-session memory, the line mode prompt hook&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="&lt;b&gt;internal/mcp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;MCP clients (stdio, HTTP), their tools wrapped as mm tools&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="&lt;b&gt;internal/structured&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;structuredOutput: Go declarations → JSON Schema (go/parser)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="&lt;b&gt;internal/agentsmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the project's AGENTS.md, appended to the system prompt per conversation&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="elicit" value="&lt;b&gt;internal/elicit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ask_user's questions and answers: normalize, line answers, elicitation form schema&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="780.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e81" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e82" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e83" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e84" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e85" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e86" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="elicit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="hooks" value="&lt;b&gt;internal/hooks&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the hooks: block — runs each hook (sh -c, JSON on stdin/stdout), chain, onError&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="940" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_engine_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_main_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_hooks_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="hooks" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_session" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_engine" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_hooks" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="hooks"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file\ No newline at end of file
modified docs/en/explanation/persistent-sessions.md +7 -0
@@ -38,6 +38,12 @@ The array sits beside `messages`, not inside it, on purpose. The model never rea
3838
3939 What it is for: asking afterwards, with the file in hand, why a run went wrong — `jq .errors <file>` for a human, or the file handed to a coding agent asked to explain the failures and propose changes to the code or the YAML (a longer `watchdogTimeout`, a different model, a smaller `maxOutput`). The `/sessions` listing and the `/resume` report show ` · ⚠️ N error(s)` so a session worth investigating stands out. The trail is capped at 200 records, oldest first out, so a run that fails on every turn does not grow its file without end.
4040
41+## Why the window's ↑ reads the stored questions
42+
43+In the window, ↑ on the box's first row recalls the questions already asked (ticket 68), and only when sessions are on. The questions are read from the session files themselves — the `user` messages of the conversation in memory, then of the project's other files, newest first — not from a separate list. That was the user's choice: nothing new is written, the recall covers every session the project has kept, including those written before the feature existed, and it disappears with them when the directory is cleaned.
44+
45+The price is that a recalled question is what the model read, not exactly what was typed: the `[attached file: …]` lines an `@path` added, a paste in full rather than its token, an `/ask` in its `[asked …]` form; slash commands, never sent to the model, are not there at all. What mm itself wrote as a user message is left out — a `/compact` summary, the note after a broken tool call, the marker of a prompt a hook blocked — and so is the context a `preGeneration` hook appended. One gap the files cannot fill: a turn that fails before answering drops its question from the history, and that is the question one most wants to retry. The window keeps it in memory until the next question, so ↑ still finds it; it is not written anywhere.
46+
4147 ## Rejected alternatives
4248
4349 - **Resume the latest session automatically at start.** Rejected for the stale-context reason above, and because docker agent does not do it either.
@@ -46,6 +52,7 @@ What it is for: asking afterwards, with the file in hand, why a run went wrong
4652 - **`session/resume` instead of `session/load`.** Rejected: it relies on the editor having kept the thread, and when the choice was made it was not in the stable spec. Since ticket 95 it is offered beside `session/load`, not instead of it.
4753 - **A `/resume` slash command over ACP.** Rejected (ticket 95): the editor draws the thread, so an old conversation replayed into an open thread would land after what it already shows, and the file would no longer match the editor's session id. Listing sessions to the editor is the protocol's way.
4854 - **Replaying only text messages.** Rejected by the user: the tool calls are the part of a coding session worth seeing again.
55+- **A separate list of typed inputs in the session file (ticket 68).** It would have recalled exactly what was typed — slash commands and paste tokens included — but it was a new field to keep in step with the history, and empty for every existing file. The user preferred the stored messages.
4956 - **Recording errors as messages in the history.** Rejected: the model would read them, `session/load` would have to replay them as something, and an old file would load differently from a new one. A separate field costs nothing on load and changes nothing for the model.
5057
5158 ## How it relates to the rest
@@ -38,6 +38,12 @@ The array sits beside `messages`, not inside it, on purpose. The model never rea
38 38
39 What it is for: asking afterwards, with the file in hand, why a run went wrong — `jq .errors <file>` for a human, or the file handed to a coding agent asked to explain the failures and propose changes to the code or the YAML (a longer `watchdogTimeout`, a different model, a smaller `maxOutput`). The `/sessions` listing and the `/resume` report show ` · ⚠️ N error(s)` so a session worth investigating stands out. The trail is capped at 200 records, oldest first out, so a run that fails on every turn does not grow its file without end.39 What it is for: asking afterwards, with the file in hand, why a run went wrong — `jq .errors <file>` for a human, or the file handed to a coding agent asked to explain the failures and propose changes to the code or the YAML (a longer `watchdogTimeout`, a different model, a smaller `maxOutput`). The `/sessions` listing and the `/resume` report show ` · ⚠️ N error(s)` so a session worth investigating stands out. The trail is capped at 200 records, oldest first out, so a run that fails on every turn does not grow its file without end.
40 40
41+## Why the window's ↑ reads the stored questions
42+
43+In the window, ↑ on the box's first row recalls the questions already asked (ticket 68), and only when sessions are on. The questions are read from the session files themselves — the `user` messages of the conversation in memory, then of the project's other files, newest first — not from a separate list. That was the user's choice: nothing new is written, the recall covers every session the project has kept, including those written before the feature existed, and it disappears with them when the directory is cleaned.
44+
45+The price is that a recalled question is what the model read, not exactly what was typed: the `[attached file: …]` lines an `@path` added, a paste in full rather than its token, an `/ask` in its `[asked …]` form; slash commands, never sent to the model, are not there at all. What mm itself wrote as a user message is left out — a `/compact` summary, the note after a broken tool call, the marker of a prompt a hook blocked — and so is the context a `preGeneration` hook appended. One gap the files cannot fill: a turn that fails before answering drops its question from the history, and that is the question one most wants to retry. The window keeps it in memory until the next question, so ↑ still finds it; it is not written anywhere.
46+
41 ## Rejected alternatives47 ## Rejected alternatives
42 48
43 - **Resume the latest session automatically at start.** Rejected for the stale-context reason above, and because docker agent does not do it either.49 - **Resume the latest session automatically at start.** Rejected for the stale-context reason above, and because docker agent does not do it either.
@@ -46,6 +52,7 @@ What it is for: asking afterwards, with the file in hand, why a run went wrong
46 - **`session/resume` instead of `session/load`.** Rejected: it relies on the editor having kept the thread, and when the choice was made it was not in the stable spec. Since ticket 95 it is offered beside `session/load`, not instead of it.52 - **`session/resume` instead of `session/load`.** Rejected: it relies on the editor having kept the thread, and when the choice was made it was not in the stable spec. Since ticket 95 it is offered beside `session/load`, not instead of it.
47 - **A `/resume` slash command over ACP.** Rejected (ticket 95): the editor draws the thread, so an old conversation replayed into an open thread would land after what it already shows, and the file would no longer match the editor's session id. Listing sessions to the editor is the protocol's way.53 - **A `/resume` slash command over ACP.** Rejected (ticket 95): the editor draws the thread, so an old conversation replayed into an open thread would land after what it already shows, and the file would no longer match the editor's session id. Listing sessions to the editor is the protocol's way.
48 - **Replaying only text messages.** Rejected by the user: the tool calls are the part of a coding session worth seeing again.54 - **Replaying only text messages.** Rejected by the user: the tool calls are the part of a coding session worth seeing again.
55+- **A separate list of typed inputs in the session file (ticket 68).** It would have recalled exactly what was typed — slash commands and paste tokens included — but it was a new field to keep in step with the history, and empty for every existing file. The user preferred the stored messages.
49 - **Recording errors as messages in the history.** Rejected: the model would read them, `session/load` would have to replay them as something, and an old file would load differently from a new one. A separate field costs nothing on load and changes nothing for the model.56 - **Recording errors as messages in the history.** Rejected: the model would read them, `session/load` would have to replay them as something, and an old file would load differently from a new one. A separate field costs nothing on load and changes nothing for the model.
50 57
51 ## How it relates to the rest58 ## How it relates to the rest
modified docs/en/how-to/keep-sessions.md +1 -0
@@ -94,6 +94,7 @@ This guide shows how to make `mm` save every conversation of a project and get o
9494 - **A project you cannot write to.** The banner or the `[acp]` log says `not saved` with the reason; the conversation goes on unpersisted.
9595 - **Compression.** A `/compact` or an automatic compression rewrites the file with the shortened history; a resumed or reloaded session starts from the summary, like the live one did. The `errors` array is left as it is.
9696 - **Errors over ACP.** The editor shows the failure in its own way; the record still goes to the thread's file, and `/new` moves it to the archived conversation with the history it belongs to.
97+- **Ask an earlier question again (window).** In `mm -tui`, ↑ on the box's first row recalls the questions of this session, then of the earlier ones. See [Recall an earlier question](use-the-window-mode.md#recall-an-earlier-question).
9798 - **Starting fresh on purpose.** Nothing to do: every start is a new session. The old ones only come back on `/resume` or on an editor reload.
9899
99100 ## See also
@@ -94,6 +94,7 @@ This guide shows how to make `mm` save every conversation of a project and get o
94 - **A project you cannot write to.** The banner or the `[acp]` log says `not saved` with the reason; the conversation goes on unpersisted.94 - **A project you cannot write to.** The banner or the `[acp]` log says `not saved` with the reason; the conversation goes on unpersisted.
95 - **Compression.** A `/compact` or an automatic compression rewrites the file with the shortened history; a resumed or reloaded session starts from the summary, like the live one did. The `errors` array is left as it is.95 - **Compression.** A `/compact` or an automatic compression rewrites the file with the shortened history; a resumed or reloaded session starts from the summary, like the live one did. The `errors` array is left as it is.
96 - **Errors over ACP.** The editor shows the failure in its own way; the record still goes to the thread's file, and `/new` moves it to the archived conversation with the history it belongs to.96 - **Errors over ACP.** The editor shows the failure in its own way; the record still goes to the thread's file, and `/new` moves it to the archived conversation with the history it belongs to.
97+- **Ask an earlier question again (window).** In `mm -tui`, ↑ on the box's first row recalls the questions of this session, then of the earlier ones. See [Recall an earlier question](use-the-window-mode.md#recall-an-earlier-question).
97 - **Starting fresh on purpose.** Nothing to do: every start is a new session. The old ones only come back on `/resume` or on an editor reload.98 - **Starting fresh on purpose.** Nothing to do: every start is a new session. The old ones only come back on `/resume` or on an editor reload.
98 99
99 ## See also100 ## See also
modified docs/en/how-to/use-the-window-mode.md +10 -0
@@ -71,6 +71,16 @@ Two details follow from "a paste is data you brought, not something you typed":
7171
7272 Pastes shorter than the threshold are inserted as text, exactly as before. Bracketed paste must be supported by the terminal for any of this to work: Terminal.app, iTerm2, Ghostty and kitty support it; inside **tmux**, a paste is passed through as bracketed only when the outer terminal and tmux both support it (tmux does since 1.8; `set -g` needs no option for pass-through, but a `paste-buffer` without `-p` sends the text as plain keys, which then arrive as typed characters and are inserted one by one).
7373
74+## Recall an earlier question
75+
76+With `sessions.enabled: true`, the box remembers what you asked.
77+
78+1. With the focus on the box, press **↑** on its first row: the last question you asked comes back in the box. Press ↑ again for the one before, and so on through this session, then through the project's earlier sessions, newest first.
79+2. Press **↓** on the box's last row to go forward again. Past the newest question, ↓ gives back what you were typing before you started looking back.
80+3. Edit the question if you want, and press **Enter** to send it. Or press **Esc** to clear the box and type something else — when no turn is running, Esc empties the box instead of cancelling.
81+
82+A recalled question is the text mm stored: an `@path` question comes back with its `[attached file: …]` lines, a collapsed paste comes back as its full text, and an `/ask` as `[asked <agent> (…): …]`. Slash commands are not stored and are not recalled. A question whose turn failed or was cancelled is recalled until you ask the next one, even though it is not kept in the session file. With sessions off, or before anything was asked, ↑ on the first row scrolls the conversation as it always did.
83+
7484 ## See also
7585
7686 - [Command line](../reference/cli.md) — the `-tui` flag and the window-mode specifics
@@ -71,6 +71,16 @@ Two details follow from "a paste is data you brought, not something you typed":
71 71
72 Pastes shorter than the threshold are inserted as text, exactly as before. Bracketed paste must be supported by the terminal for any of this to work: Terminal.app, iTerm2, Ghostty and kitty support it; inside **tmux**, a paste is passed through as bracketed only when the outer terminal and tmux both support it (tmux does since 1.8; `set -g` needs no option for pass-through, but a `paste-buffer` without `-p` sends the text as plain keys, which then arrive as typed characters and are inserted one by one).72 Pastes shorter than the threshold are inserted as text, exactly as before. Bracketed paste must be supported by the terminal for any of this to work: Terminal.app, iTerm2, Ghostty and kitty support it; inside **tmux**, a paste is passed through as bracketed only when the outer terminal and tmux both support it (tmux does since 1.8; `set -g` needs no option for pass-through, but a `paste-buffer` without `-p` sends the text as plain keys, which then arrive as typed characters and are inserted one by one).
73 73
74+## Recall an earlier question
75+
76+With `sessions.enabled: true`, the box remembers what you asked.
77+
78+1. With the focus on the box, press **↑** on its first row: the last question you asked comes back in the box. Press ↑ again for the one before, and so on through this session, then through the project's earlier sessions, newest first.
79+2. Press **↓** on the box's last row to go forward again. Past the newest question, ↓ gives back what you were typing before you started looking back.
80+3. Edit the question if you want, and press **Enter** to send it. Or press **Esc** to clear the box and type something else — when no turn is running, Esc empties the box instead of cancelling.
81+
82+A recalled question is the text mm stored: an `@path` question comes back with its `[attached file: …]` lines, a collapsed paste comes back as its full text, and an `/ask` as `[asked <agent> (…): …]`. Slash commands are not stored and are not recalled. A question whose turn failed or was cancelled is recalled until you ask the next one, even though it is not kept in the session file. With sessions off, or before anything was asked, ↑ on the first row scrolls the conversation as it always did.
83+
74 ## See also84 ## See also
75 85
76 - [Command line](../reference/cli.md) — the `-tui` flag and the window-mode specifics86 - [Command line](../reference/cli.md) — the `-tui` flag and the window-mode specifics
modified docs/en/reference/slash-commands.md +2 -2
@@ -96,10 +96,10 @@ The keys:
9696 |-----|--------|
9797 | `Enter` | Sends the box (nothing on an empty box). With the picker open on an incomplete name, completes it instead. |
9898 | `Ctrl-J`, `Alt-Enter` | Inserts a newline in the box. |
99-| `Esc` | Cancels the running turn (same as `/abort`); closes the picker; rejects a pending permission. |
99+| `Esc` | Cancels the running turn (same as `/abort`); when no turn runs and the box is not empty, clears the box instead; closes the picker; rejects a pending permission. |
100100 | `Ctrl-C` | Cancels the running turn; quits when idle — and quits too when the turn has already been asked to stop and has not. |
101101 | `Tab` | Moves the focus between the box and the conversation; completes the picker's choice when it is open (`/`, `@` or `$`). |
102-| `↑` `↓` | In the box: move between its rows — a long line wraps at spaces into several rows — and scroll the conversation from its first/last row. On the conversation: scroll one line. In the picker: move the choice. |
102+| `↑` `↓` | In the box: move between its rows — a long line wraps at spaces into several rows. From the first row, `↑` recalls the previous question (sessions on: this session, then the earlier ones, newest first); from the last row during a recall, `↓` the next one, then the text typed before. With nothing to recall, they scroll the conversation from the first/last row. On the conversation: scroll one line. In the picker: move the choice. |
103103 | `PgUp` `PgDn` | Scroll one page. |
104104 | `Home` `End` | In the box: start/end of the line. On the conversation: top / bottom (bottom resumes following). |
105105 | `Ctrl-L` | Clears the displayed conversation; the history is kept. |
@@ -96,10 +96,10 @@ The keys:
96 |-----|--------|96 |-----|--------|
97 | `Enter` | Sends the box (nothing on an empty box). With the picker open on an incomplete name, completes it instead. |97 | `Enter` | Sends the box (nothing on an empty box). With the picker open on an incomplete name, completes it instead. |
98 | `Ctrl-J`, `Alt-Enter` | Inserts a newline in the box. |98 | `Ctrl-J`, `Alt-Enter` | Inserts a newline in the box. |
99-| `Esc` | Cancels the running turn (same as `/abort`); closes the picker; rejects a pending permission. |99+| `Esc` | Cancels the running turn (same as `/abort`); when no turn runs and the box is not empty, clears the box instead; closes the picker; rejects a pending permission. |
100 | `Ctrl-C` | Cancels the running turn; quits when idle — and quits too when the turn has already been asked to stop and has not. |100 | `Ctrl-C` | Cancels the running turn; quits when idle — and quits too when the turn has already been asked to stop and has not. |
101 | `Tab` | Moves the focus between the box and the conversation; completes the picker's choice when it is open (`/`, `@` or `$`). |101 | `Tab` | Moves the focus between the box and the conversation; completes the picker's choice when it is open (`/`, `@` or `$`). |
102-| `↑` `↓` | In the box: move between its rows — a long line wraps at spaces into several rows — and scroll the conversation from its first/last row. On the conversation: scroll one line. In the picker: move the choice. |102+| `↑` `↓` | In the box: move between its rows — a long line wraps at spaces into several rows. From the first row, `↑` recalls the previous question (sessions on: this session, then the earlier ones, newest first); from the last row during a recall, `↓` the next one, then the text typed before. With nothing to recall, they scroll the conversation from the first/last row. On the conversation: scroll one line. In the picker: move the choice. |
103 | `PgUp` `PgDn` | Scroll one page. |103 | `PgUp` `PgDn` | Scroll one page. |
104 | `Home` `End` | In the box: start/end of the line. On the conversation: top / bottom (bottom resumes following). |104 | `Home` `End` | In the box: start/end of the line. On the conversation: top / bottom (bottom resumes following). |
105 | `Ctrl-L` | Clears the displayed conversation; the history is kept. |105 | `Ctrl-L` | Clears the displayed conversation; the history is kept. |
modified docs/fr/explanation/persistent-sessions.md +7 -0
@@ -38,6 +38,12 @@ Le tableau est à côté de `messages`, pas dedans, à dessein. Le modèle ne le
3838
3939 À quoi cela sert : demander après coup, le fichier en main, pourquoi une exécution a mal tourné — `jq .errors <fichier>` pour un humain, ou le fichier confié à un agent de code à qui l'on demande d'expliquer les échecs et de proposer des changements de code ou de YAML (un `watchdogTimeout` plus long, un autre modèle, un `maxOutput` plus petit). La liste `/sessions` et le compte rendu de `/resume` affichent ` · ⚠️ N error(s)` pour qu'une session à examiner se remarque. La trace est plafonnée à 200 enregistrements, les plus anciens sortent d'abord, pour qu'une exécution qui échoue à chaque tour ne fasse pas grossir son fichier sans fin.
4040
41+## Pourquoi le ↑ de la fenêtre lit les questions enregistrées
42+
43+Dans la fenêtre, ↑ depuis la première rangée de la zone rappelle les questions déjà posées (ticket 68), et seulement quand les sessions sont activées. Les questions sont lues dans les fichiers de session eux-mêmes — les messages `user` de la conversation en mémoire, puis des autres fichiers du projet, du plus récent au plus ancien — et non dans une liste à part. C'est le choix de l'utilisateur : rien de nouveau n'est écrit, le rappel couvre toutes les sessions que le projet a gardées, y compris celles écrites avant la fonctionnalité, et il disparaît avec elles quand on vide le répertoire.
44+
45+La contrepartie : une question rappelée est ce que le modèle a lu, pas exactement ce qui a été tapé — les lignes `[attached file: …]` ajoutées par un `@chemin`, un collage en entier plutôt que son jeton, un `/ask` sous sa forme `[asked …]` ; les commandes `/`, jamais envoyées au modèle, n'y sont pas du tout. Ce que mm a écrit lui-même comme message utilisateur est écarté — un résumé de `/compact`, la note après un appel d'outil cassé, le marqueur d'un prompt bloqué par un hook — de même que le contexte ajouté par un hook `preGeneration`. Un trou que les fichiers ne peuvent pas combler : un tour qui échoue avant de répondre retire sa question de l'historique, et c'est justement celle qu'on veut relancer. La fenêtre la garde en mémoire jusqu'à la question suivante, pour que ↑ la retrouve ; elle n'est écrite nulle part.
46+
4147 ## Alternatives écartées
4248
4349 - **Reprendre automatiquement la dernière session au lancement.** Écartée pour la raison du contexte périmé, et parce que docker agent ne le fait pas non plus.
@@ -46,6 +52,7 @@ Le tableau est à côté de `messages`, pas dedans, à dessein. Le modèle ne le
4652 - **`session/resume` plutôt que `session/load`.** Écarté : dépendant d'un éditeur qui aurait gardé le fil, et hors de la spécification stable quand le choix a été fait. Depuis le ticket 95, il est proposé à côté de `session/load`, pas à sa place.
4753 - **Une commande `/resume` en ACP.** Écartée (ticket 95) : c'est l'éditeur qui dessine le fil, donc une ancienne conversation rejouée dans un fil ouvert arriverait après ce qu'il affiche déjà, et le fichier ne correspondrait plus à l'identifiant de session de l'éditeur. Lister les sessions à l'éditeur est la voie du protocole.
4854 - **Ne rejouer que les messages texte.** Écarté par l'utilisateur : les appels d'outils sont la partie d'une session de code qui vaut d'être revue.
55+- **Une liste à part des saisies dans le fichier de session (ticket 68).** Elle aurait rappelé exactement ce qui a été tapé — commandes `/` et jetons de collage compris — mais c'était un nouveau champ à tenir en phase avec l'historique, vide pour tous les fichiers existants. L'utilisateur a préféré les messages enregistrés.
4956 - **Enregistrer les erreurs comme des messages de l'historique.** Écarté : le modèle les lirait, `session/load` devrait les rejouer sous une forme ou une autre, et un vieux fichier se chargerait autrement qu'un nouveau. Un champ séparé ne coûte rien au chargement et ne change rien pour le modèle.
5057
5158 ## Liens avec le reste
@@ -38,6 +38,12 @@ Le tableau est à côté de `messages`, pas dedans, à dessein. Le modèle ne le
38 38
39 À quoi cela sert : demander après coup, le fichier en main, pourquoi une exécution a mal tourné — `jq .errors <fichier>` pour un humain, ou le fichier confié à un agent de code à qui l'on demande d'expliquer les échecs et de proposer des changements de code ou de YAML (un `watchdogTimeout` plus long, un autre modèle, un `maxOutput` plus petit). La liste `/sessions` et le compte rendu de `/resume` affichent ` · ⚠️ N error(s)` pour qu'une session à examiner se remarque. La trace est plafonnée à 200 enregistrements, les plus anciens sortent d'abord, pour qu'une exécution qui échoue à chaque tour ne fasse pas grossir son fichier sans fin.39 À quoi cela sert : demander après coup, le fichier en main, pourquoi une exécution a mal tourné — `jq .errors <fichier>` pour un humain, ou le fichier confié à un agent de code à qui l'on demande d'expliquer les échecs et de proposer des changements de code ou de YAML (un `watchdogTimeout` plus long, un autre modèle, un `maxOutput` plus petit). La liste `/sessions` et le compte rendu de `/resume` affichent ` · ⚠️ N error(s)` pour qu'une session à examiner se remarque. La trace est plafonnée à 200 enregistrements, les plus anciens sortent d'abord, pour qu'une exécution qui échoue à chaque tour ne fasse pas grossir son fichier sans fin.
40 40
41+## Pourquoi le ↑ de la fenêtre lit les questions enregistrées
42+
43+Dans la fenêtre, ↑ depuis la première rangée de la zone rappelle les questions déjà posées (ticket 68), et seulement quand les sessions sont activées. Les questions sont lues dans les fichiers de session eux-mêmes — les messages `user` de la conversation en mémoire, puis des autres fichiers du projet, du plus récent au plus ancien — et non dans une liste à part. C'est le choix de l'utilisateur : rien de nouveau n'est écrit, le rappel couvre toutes les sessions que le projet a gardées, y compris celles écrites avant la fonctionnalité, et il disparaît avec elles quand on vide le répertoire.
44+
45+La contrepartie : une question rappelée est ce que le modèle a lu, pas exactement ce qui a été tapé — les lignes `[attached file: …]` ajoutées par un `@chemin`, un collage en entier plutôt que son jeton, un `/ask` sous sa forme `[asked …]` ; les commandes `/`, jamais envoyées au modèle, n'y sont pas du tout. Ce que mm a écrit lui-même comme message utilisateur est écarté — un résumé de `/compact`, la note après un appel d'outil cassé, le marqueur d'un prompt bloqué par un hook — de même que le contexte ajouté par un hook `preGeneration`. Un trou que les fichiers ne peuvent pas combler : un tour qui échoue avant de répondre retire sa question de l'historique, et c'est justement celle qu'on veut relancer. La fenêtre la garde en mémoire jusqu'à la question suivante, pour que ↑ la retrouve ; elle n'est écrite nulle part.
46+
41 ## Alternatives écartées47 ## Alternatives écartées
42 48
43 - **Reprendre automatiquement la dernière session au lancement.** Écartée pour la raison du contexte périmé, et parce que docker agent ne le fait pas non plus.49 - **Reprendre automatiquement la dernière session au lancement.** Écartée pour la raison du contexte périmé, et parce que docker agent ne le fait pas non plus.
@@ -46,6 +52,7 @@ Le tableau est à côté de `messages`, pas dedans, à dessein. Le modèle ne le
46 - **`session/resume` plutôt que `session/load`.** Écarté : dépendant d'un éditeur qui aurait gardé le fil, et hors de la spécification stable quand le choix a été fait. Depuis le ticket 95, il est proposé à côté de `session/load`, pas à sa place.52 - **`session/resume` plutôt que `session/load`.** Écarté : dépendant d'un éditeur qui aurait gardé le fil, et hors de la spécification stable quand le choix a été fait. Depuis le ticket 95, il est proposé à côté de `session/load`, pas à sa place.
47 - **Une commande `/resume` en ACP.** Écartée (ticket 95) : c'est l'éditeur qui dessine le fil, donc une ancienne conversation rejouée dans un fil ouvert arriverait après ce qu'il affiche déjà, et le fichier ne correspondrait plus à l'identifiant de session de l'éditeur. Lister les sessions à l'éditeur est la voie du protocole.53 - **Une commande `/resume` en ACP.** Écartée (ticket 95) : c'est l'éditeur qui dessine le fil, donc une ancienne conversation rejouée dans un fil ouvert arriverait après ce qu'il affiche déjà, et le fichier ne correspondrait plus à l'identifiant de session de l'éditeur. Lister les sessions à l'éditeur est la voie du protocole.
48 - **Ne rejouer que les messages texte.** Écarté par l'utilisateur : les appels d'outils sont la partie d'une session de code qui vaut d'être revue.54 - **Ne rejouer que les messages texte.** Écarté par l'utilisateur : les appels d'outils sont la partie d'une session de code qui vaut d'être revue.
55+- **Une liste à part des saisies dans le fichier de session (ticket 68).** Elle aurait rappelé exactement ce qui a été tapé — commandes `/` et jetons de collage compris — mais c'était un nouveau champ à tenir en phase avec l'historique, vide pour tous les fichiers existants. L'utilisateur a préféré les messages enregistrés.
49 - **Enregistrer les erreurs comme des messages de l'historique.** Écarté : le modèle les lirait, `session/load` devrait les rejouer sous une forme ou une autre, et un vieux fichier se chargerait autrement qu'un nouveau. Un champ séparé ne coûte rien au chargement et ne change rien pour le modèle.56 - **Enregistrer les erreurs comme des messages de l'historique.** Écarté : le modèle les lirait, `session/load` devrait les rejouer sous une forme ou une autre, et un vieux fichier se chargerait autrement qu'un nouveau. Un champ séparé ne coûte rien au chargement et ne change rien pour le modèle.
50 57
51 ## Liens avec le reste58 ## Liens avec le reste
modified docs/fr/how-to/keep-sessions.md +1 -0
@@ -94,6 +94,7 @@ Ce guide montre comment faire enregistrer par `mm` chaque conversation d'un proj
9494 - **Un projet en lecture seule.** La bannière ou le journal `[acp]` indique `not saved` avec la raison ; la conversation continue sans être enregistrée.
9595 - **Compression.** Un `/compact` ou une compression automatique réécrit le fichier avec l'historique raccourci ; une session reprise ou rechargée repart du résumé, comme la session en direct. Le tableau `errors` reste tel quel.
9696 - **Erreurs en ACP.** L'éditeur montre l'échec à sa manière ; l'enregistrement va quand même dans le fichier du fil, et `/new` le déplace dans la conversation archivée avec l'historique auquel il appartient.
97+- **Reposer une question précédente (fenêtre).** Dans `mm -tui`, ↑ depuis la première rangée de la zone rappelle les questions de cette session, puis des précédentes. Voir [Rappeler une question précédente](use-the-window-mode.md#rappeler-une-question-précédente).
9798 - **Repartir de zéro volontairement.** Rien à faire : chaque lancement est une nouvelle session. Les anciennes ne reviennent que sur `/resume` ou sur un rechargement depuis l'éditeur.
9899
99100 ## Voir aussi
@@ -94,6 +94,7 @@ Ce guide montre comment faire enregistrer par `mm` chaque conversation d'un proj
94 - **Un projet en lecture seule.** La bannière ou le journal `[acp]` indique `not saved` avec la raison ; la conversation continue sans être enregistrée.94 - **Un projet en lecture seule.** La bannière ou le journal `[acp]` indique `not saved` avec la raison ; la conversation continue sans être enregistrée.
95 - **Compression.** Un `/compact` ou une compression automatique réécrit le fichier avec l'historique raccourci ; une session reprise ou rechargée repart du résumé, comme la session en direct. Le tableau `errors` reste tel quel.95 - **Compression.** Un `/compact` ou une compression automatique réécrit le fichier avec l'historique raccourci ; une session reprise ou rechargée repart du résumé, comme la session en direct. Le tableau `errors` reste tel quel.
96 - **Erreurs en ACP.** L'éditeur montre l'échec à sa manière ; l'enregistrement va quand même dans le fichier du fil, et `/new` le déplace dans la conversation archivée avec l'historique auquel il appartient.96 - **Erreurs en ACP.** L'éditeur montre l'échec à sa manière ; l'enregistrement va quand même dans le fichier du fil, et `/new` le déplace dans la conversation archivée avec l'historique auquel il appartient.
97+- **Reposer une question précédente (fenêtre).** Dans `mm -tui`, ↑ depuis la première rangée de la zone rappelle les questions de cette session, puis des précédentes. Voir [Rappeler une question précédente](use-the-window-mode.md#rappeler-une-question-précédente).
97 - **Repartir de zéro volontairement.** Rien à faire : chaque lancement est une nouvelle session. Les anciennes ne reviennent que sur `/resume` ou sur un rechargement depuis l'éditeur.98 - **Repartir de zéro volontairement.** Rien à faire : chaque lancement est une nouvelle session. Les anciennes ne reviennent que sur `/resume` ou sur un rechargement depuis l'éditeur.
98 99
99 ## Voir aussi100 ## Voir aussi
modified docs/fr/how-to/use-the-window-mode.md +10 -0
@@ -71,6 +71,16 @@ Deux détails découlent de « un collage est une donnée que vous apportez, pas
7171
7272 Les collages plus courts que le seuil sont insérés comme du texte, exactement comme avant. Le terminal doit prendre en charge le collage encadré (bracketed paste) pour que tout cela fonctionne : Terminal.app, iTerm2, Ghostty et kitty le font ; dans **tmux**, un collage n'arrive encadré que si le terminal extérieur et tmux le prennent en charge tous deux (tmux le fait depuis la 1.8 ; aucune option `set -g` n'est nécessaire pour le passage, mais un `paste-buffer` sans `-p` envoie le texte comme des touches ordinaires, qui arrivent alors comme des caractères tapés et sont insérés un à un).
7373
74+## Rappeler une question précédente
75+
76+Avec `sessions.enabled: true`, la zone de saisie se souvient de ce que vous avez demandé.
77+
78+1. Le focus sur la zone, appuyez sur **↑** depuis sa première rangée : votre dernière question revient dans la zone. Appuyez encore sur ↑ pour la précédente, et ainsi de suite dans cette session, puis dans les sessions précédentes du projet, de la plus récente à la plus ancienne.
79+2. Appuyez sur **↓** depuis la dernière rangée de la zone pour revenir vers les plus récentes. Après la plus récente, ↓ rend ce que vous tapiez avant de remonter.
80+3. Modifiez la question si besoin, puis appuyez sur **Entrée** pour l'envoyer. Ou appuyez sur **Échap** pour vider la zone et taper autre chose — quand aucun tour ne tourne, Échap vide la zone au lieu d'annuler.
81+
82+Une question rappelée est le texte que mm a enregistré : une question avec `@chemin` revient avec ses lignes `[attached file: …]`, un collage replié revient en texte complet, un `/ask` sous la forme `[asked <agent> (…): …]`. Les commandes `/` ne sont pas enregistrées et ne sont pas rappelées. La question d'un tour qui a échoué ou a été annulé reste rappelable jusqu'à la question suivante, bien qu'elle ne soit pas gardée dans le fichier de session. Sessions désactivées, ou avant toute question, ↑ depuis la première rangée fait défiler la conversation comme avant.
83+
7484 ## Voir aussi
7585
7686 - [Ligne de commande](../reference/cli.md) — l'option `-tui` et les spécificités du mode fenêtre
@@ -71,6 +71,16 @@ Deux détails découlent de « un collage est une donnée que vous apportez, pas
71 71
72 Les collages plus courts que le seuil sont insérés comme du texte, exactement comme avant. Le terminal doit prendre en charge le collage encadré (bracketed paste) pour que tout cela fonctionne : Terminal.app, iTerm2, Ghostty et kitty le font ; dans **tmux**, un collage n'arrive encadré que si le terminal extérieur et tmux le prennent en charge tous deux (tmux le fait depuis la 1.8 ; aucune option `set -g` n'est nécessaire pour le passage, mais un `paste-buffer` sans `-p` envoie le texte comme des touches ordinaires, qui arrivent alors comme des caractères tapés et sont insérés un à un).72 Les collages plus courts que le seuil sont insérés comme du texte, exactement comme avant. Le terminal doit prendre en charge le collage encadré (bracketed paste) pour que tout cela fonctionne : Terminal.app, iTerm2, Ghostty et kitty le font ; dans **tmux**, un collage n'arrive encadré que si le terminal extérieur et tmux le prennent en charge tous deux (tmux le fait depuis la 1.8 ; aucune option `set -g` n'est nécessaire pour le passage, mais un `paste-buffer` sans `-p` envoie le texte comme des touches ordinaires, qui arrivent alors comme des caractères tapés et sont insérés un à un).
73 73
74+## Rappeler une question précédente
75+
76+Avec `sessions.enabled: true`, la zone de saisie se souvient de ce que vous avez demandé.
77+
78+1. Le focus sur la zone, appuyez sur **↑** depuis sa première rangée : votre dernière question revient dans la zone. Appuyez encore sur ↑ pour la précédente, et ainsi de suite dans cette session, puis dans les sessions précédentes du projet, de la plus récente à la plus ancienne.
79+2. Appuyez sur **↓** depuis la dernière rangée de la zone pour revenir vers les plus récentes. Après la plus récente, ↓ rend ce que vous tapiez avant de remonter.
80+3. Modifiez la question si besoin, puis appuyez sur **Entrée** pour l'envoyer. Ou appuyez sur **Échap** pour vider la zone et taper autre chose — quand aucun tour ne tourne, Échap vide la zone au lieu d'annuler.
81+
82+Une question rappelée est le texte que mm a enregistré : une question avec `@chemin` revient avec ses lignes `[attached file: …]`, un collage replié revient en texte complet, un `/ask` sous la forme `[asked <agent> (…): …]`. Les commandes `/` ne sont pas enregistrées et ne sont pas rappelées. La question d'un tour qui a échoué ou a été annulé reste rappelable jusqu'à la question suivante, bien qu'elle ne soit pas gardée dans le fichier de session. Sessions désactivées, ou avant toute question, ↑ depuis la première rangée fait défiler la conversation comme avant.
83+
74 ## Voir aussi84 ## Voir aussi
75 85
76 - [Ligne de commande](../reference/cli.md) — l'option `-tui` et les spécificités du mode fenêtre86 - [Ligne de commande](../reference/cli.md) — l'option `-tui` et les spécificités du mode fenêtre
modified docs/fr/reference/slash-commands.md +2 -2
@@ -96,10 +96,10 @@ Les touches :
9696 |--------|--------|
9797 | `Entrée` | Envoie la zone (rien sur une zone vide). Avec le sélecteur ouvert sur un nom incomplet, le complète à la place. |
9898 | `Ctrl-J`, `Alt-Entrée` | Insère un saut de ligne dans la zone. |
99-| `Échap` | Annule le tour en cours (comme `/abort`) ; ferme le sélecteur ; refuse une permission en attente. |
99+| `Échap` | Annule le tour en cours (comme `/abort`) ; quand aucun tour ne tourne et que la zone n'est pas vide, vide la zone à la place ; ferme le sélecteur ; refuse une permission en attente. |
100100 | `Ctrl-C` | Annule le tour en cours ; quitte au repos — et quitte aussi lorsque le tour a déjà reçu l'ordre de s'arrêter et ne l'a pas fait. |
101101 | `Tab` | Déplace le focus entre la zone et la conversation ; complète le choix du sélecteur quand il est ouvert (`/`, `@` ou `$`). |
102-| `↑` `↓` | Dans la zone : passent d'une rangée à l'autre — une ligne longue est repliée aux espaces sur plusieurs rangées — et font défiler la conversation depuis sa première/dernière rangée. Sur la conversation : défilent d'une ligne. Dans le sélecteur : déplacent le choix. |
102+| `↑` `↓` | Dans la zone : passent d'une rangée à l'autre — une ligne longue est repliée aux espaces sur plusieurs rangées. Depuis la première rangée, `↑` rappelle la question précédente (sessions activées : cette session, puis les précédentes, de la plus récente à la plus ancienne) ; depuis la dernière rangée pendant un rappel, `↓` la suivante, puis le texte tapé avant. Sans rien à rappeler, ils font défiler la conversation depuis la première/dernière rangée. Sur la conversation : défilent d'une ligne. Dans le sélecteur : déplacent le choix. |
103103 | `PgUp` `PgDn` | Défilent d'une page. |
104104 | `Début` `Fin` | Dans la zone : début/fin de ligne. Sur la conversation : haut / bas (le bas reprend le suivi). |
105105 | `Ctrl-L` | Efface la conversation affichée ; l'historique est conservé. |
@@ -96,10 +96,10 @@ Les touches :
96 |--------|--------|96 |--------|--------|
97 | `Entrée` | Envoie la zone (rien sur une zone vide). Avec le sélecteur ouvert sur un nom incomplet, le complète à la place. |97 | `Entrée` | Envoie la zone (rien sur une zone vide). Avec le sélecteur ouvert sur un nom incomplet, le complète à la place. |
98 | `Ctrl-J`, `Alt-Entrée` | Insère un saut de ligne dans la zone. |98 | `Ctrl-J`, `Alt-Entrée` | Insère un saut de ligne dans la zone. |
99-| `Échap` | Annule le tour en cours (comme `/abort`) ; ferme le sélecteur ; refuse une permission en attente. |99+| `Échap` | Annule le tour en cours (comme `/abort`) ; quand aucun tour ne tourne et que la zone n'est pas vide, vide la zone à la place ; ferme le sélecteur ; refuse une permission en attente. |
100 | `Ctrl-C` | Annule le tour en cours ; quitte au repos — et quitte aussi lorsque le tour a déjà reçu l'ordre de s'arrêter et ne l'a pas fait. |100 | `Ctrl-C` | Annule le tour en cours ; quitte au repos — et quitte aussi lorsque le tour a déjà reçu l'ordre de s'arrêter et ne l'a pas fait. |
101 | `Tab` | Déplace le focus entre la zone et la conversation ; complète le choix du sélecteur quand il est ouvert (`/`, `@` ou `$`). |101 | `Tab` | Déplace le focus entre la zone et la conversation ; complète le choix du sélecteur quand il est ouvert (`/`, `@` ou `$`). |
102-| `↑` `↓` | Dans la zone : passent d'une rangée à l'autre — une ligne longue est repliée aux espaces sur plusieurs rangées — et font défiler la conversation depuis sa première/dernière rangée. Sur la conversation : défilent d'une ligne. Dans le sélecteur : déplacent le choix. |102+| `↑` `↓` | Dans la zone : passent d'une rangée à l'autre — une ligne longue est repliée aux espaces sur plusieurs rangées. Depuis la première rangée, `↑` rappelle la question précédente (sessions activées : cette session, puis les précédentes, de la plus récente à la plus ancienne) ; depuis la dernière rangée pendant un rappel, `↓` la suivante, puis le texte tapé avant. Sans rien à rappeler, ils font défiler la conversation depuis la première/dernière rangée. Sur la conversation : défilent d'une ligne. Dans le sélecteur : déplacent le choix. |
103 | `PgUp` `PgDn` | Défilent d'une page. |103 | `PgUp` `PgDn` | Défilent d'une page. |
104 | `Début` `Fin` | Dans la zone : début/fin de ligne. Sur la conversation : haut / bas (le bas reprend le suivi). |104 | `Début` `Fin` | Dans la zone : début/fin de ligne. Sur la conversation : haut / bas (le bas reprend le suivi). |
105 | `Ctrl-L` | Efface la conversation affichée ; l'historique est conservé. |105 | `Ctrl-L` | Efface la conversation affichée ; l'historique est conservé. |
added internal/engine/notes.go +18 -0
new file mode 100644
@@ -0,0 +1,18 @@
1+package engine
2+
3+import "strings"
4+
5+// argsRetryPrefix opens the note argsRetryNote hands the model in place of a
6+// tool call whose arguments did not parse.
7+const argsRetryPrefix = "[mm: "
8+
9+// IsNote reports whether a user message's text was written by mm itself —
10+// the retry note after a broken tool call, the marker left where a hook
11+// blocked a prompt — rather than typed by the user. A front end recalling
12+// the questions it was asked skips these.
13+//
14+// engine.IsNote("[prompt blocked by a hook: secret]") // true
15+// engine.IsNote("explain main.go") // false
16+func IsNote(text string) bool {
17+ return strings.HasPrefix(text, argsRetryPrefix) || strings.HasPrefix(text, blockedPrompt)
18+}
new file mode 100644
@@ -0,0 +1,18 @@
1+package engine
2+
3+import "strings"
4+
5+// argsRetryPrefix opens the note argsRetryNote hands the model in place of a
6+// tool call whose arguments did not parse.
7+const argsRetryPrefix = "[mm: "
8+
9+// IsNote reports whether a user message's text was written by mm itself —
10+// the retry note after a broken tool call, the marker left where a hook
11+// blocked a prompt — rather than typed by the user. A front end recalling
12+// the questions it was asked skips these.
13+//
14+// engine.IsNote("[prompt blocked by a hook: secret]") // true
15+// engine.IsNote("explain main.go") // false
16+func IsNote(text string) bool {
17+ return strings.HasPrefix(text, argsRetryPrefix) || strings.HasPrefix(text, blockedPrompt)
18+}
added internal/engine/notes_test.go +23 -0
new file mode 100644
@@ -0,0 +1,23 @@
1+package engine
2+
3+import "testing"
4+
5+// The notes mm writes into the history are recognised from the very messages
6+// that write them, so a reworded note cannot slip past IsNote.
7+func TestIsNoteRecognisesMmOwnMessages(t *testing.T) {
8+ for _, text := range []string{
9+ argsRetryNote(true).Text(),
10+ argsRetryNote(false).Text(),
11+ blockedMarker(""),
12+ blockedMarker("a secret"),
13+ } {
14+ if !IsNote(text) {
15+ t.Errorf("IsNote(%q) = false, want true", text)
16+ }
17+ }
18+ for _, text := range []string{"explain main.go", "[draft] fix the tests", ""} {
19+ if IsNote(text) {
20+ t.Errorf("IsNote(%q) = true, want false", text)
21+ }
22+ }
23+}
new file mode 100644
@@ -0,0 +1,23 @@
1+package engine
2+
3+import "testing"
4+
5+// The notes mm writes into the history are recognised from the very messages
6+// that write them, so a reworded note cannot slip past IsNote.
7+func TestIsNoteRecognisesMmOwnMessages(t *testing.T) {
8+ for _, text := range []string{
9+ argsRetryNote(true).Text(),
10+ argsRetryNote(false).Text(),
11+ blockedMarker(""),
12+ blockedMarker("a secret"),
13+ } {
14+ if !IsNote(text) {
15+ t.Errorf("IsNote(%q) = false, want true", text)
16+ }
17+ }
18+ for _, text := range []string{"explain main.go", "[draft] fix the tests", ""} {
19+ if IsNote(text) {
20+ t.Errorf("IsNote(%q) = true, want false", text)
21+ }
22+ }
23+}
modified internal/engine/toolargs.go +1 -1
@@ -71,7 +71,7 @@ func pieceChars() int {
7171 // the call never became one.
7272 func argsRetryNote(truncated bool) *ai.Message {
7373 var b strings.Builder
74- b.WriteString("[mm: your last tool call was not run: ")
74+ b.WriteString(argsRetryPrefix + "your last tool call was not run: ")
7575 if truncated {
7676 b.WriteString("it was cut off before its end")
7777 if mt := maxTokens(); mt != "" {
@@ -71,7 +71,7 @@ func pieceChars() int {
71 // the call never became one.71 // the call never became one.
72 func argsRetryNote(truncated bool) *ai.Message {72 func argsRetryNote(truncated bool) *ai.Message {
73 var b strings.Builder73 var b strings.Builder
74- b.WriteString("[mm: your last tool call was not run: ")74+ b.WriteString(argsRetryPrefix + "your last tool call was not run: ")
75 if truncated {75 if truncated {
76 b.WriteString("it was cut off before its end")76 b.WriteString("it was cut off before its end")
77 if mt := maxTokens(); mt != "" {77 if mt := maxTokens(); mt != "" {
added internal/store/questions.go +71 -0
new file mode 100644
@@ -0,0 +1,71 @@
1+package store
2+
3+import (
4+ "strings"
5+
6+ "mm/internal/compact"
7+ "mm/internal/engine"
8+ "mm/internal/hooks"
9+
10+ "github.com/firebase/genkit/go/ai"
11+)
12+
13+// maxQuestions caps what Questions returns: enough to scroll back through
14+// days of work, few enough that a long-lived project does not hand the
15+// window thousands of entries to step through.
16+const maxQuestions = 200
17+
18+// Questions is what the window's ↑ steps back through (ticket 68): the user
19+// questions of the conversation in progress, newest first, then those of the
20+// project's other stored sessions, the most recently updated first. They are
21+// read from the stored history, so a question comes back as mm kept it —
22+// with its `[attached file: …]` lines and its pastes in full. What the user
23+// never typed is left out: a /compact summary, mm's own notes (engine.IsNote),
24+// and the context a preGeneration hook appended. Two identical questions in
25+// a row are kept once. A nil Keeper — sessions off — has nothing to recall.
26+//
27+// recall := keep.Questions(messages) // recall[0] is the last question asked
28+func (k *Keeper) Questions(current []*ai.Message) []string {
29+ if k == nil {
30+ return nil
31+ }
32+ var out []string
33+ add := func(msgs []*ai.Message) bool {
34+ for i := len(msgs) - 1; i >= 0; i-- {
35+ q, ok := question(msgs[i])
36+ if !ok || (len(out) > 0 && out[len(out)-1] == q) {
37+ continue
38+ }
39+ if out = append(out, q); len(out) == maxQuestions {
40+ return false
41+ }
42+ }
43+ return true
44+ }
45+ if !add(current) {
46+ return out
47+ }
48+ for _, sess := range k.st.all() {
49+ if k.rec != nil && sess.ID == k.rec.ID {
50+ continue // its questions are in current, as they stand now
51+ }
52+ if !add(sess.Messages) {
53+ break
54+ }
55+ }
56+ return out
57+}
58+
59+// question is the text a user message holds as the user typed it, and
60+// whether it is a question at all.
61+func question(m *ai.Message) (string, bool) {
62+ if m == nil || m.Role != ai.RoleUser || compact.IsSummary(m) {
63+ return "", false
64+ }
65+ text, _, _ := strings.Cut(m.Text(), "\n\n"+hooks.ContextLabel)
66+ text = strings.TrimSpace(text)
67+ if text == "" || engine.IsNote(text) {
68+ return "", false
69+ }
70+ return text, true
71+}
new file mode 100644
@@ -0,0 +1,71 @@
1+package store
2+
3+import (
4+ "strings"
5+
6+ "mm/internal/compact"
7+ "mm/internal/engine"
8+ "mm/internal/hooks"
9+
10+ "github.com/firebase/genkit/go/ai"
11+)
12+
13+// maxQuestions caps what Questions returns: enough to scroll back through
14+// days of work, few enough that a long-lived project does not hand the
15+// window thousands of entries to step through.
16+const maxQuestions = 200
17+
18+// Questions is what the window's ↑ steps back through (ticket 68): the user
19+// questions of the conversation in progress, newest first, then those of the
20+// project's other stored sessions, the most recently updated first. They are
21+// read from the stored history, so a question comes back as mm kept it —
22+// with its `[attached file: …]` lines and its pastes in full. What the user
23+// never typed is left out: a /compact summary, mm's own notes (engine.IsNote),
24+// and the context a preGeneration hook appended. Two identical questions in
25+// a row are kept once. A nil Keeper — sessions off — has nothing to recall.
26+//
27+// recall := keep.Questions(messages) // recall[0] is the last question asked
28+func (k *Keeper) Questions(current []*ai.Message) []string {
29+ if k == nil {
30+ return nil
31+ }
32+ var out []string
33+ add := func(msgs []*ai.Message) bool {
34+ for i := len(msgs) - 1; i >= 0; i-- {
35+ q, ok := question(msgs[i])
36+ if !ok || (len(out) > 0 && out[len(out)-1] == q) {
37+ continue
38+ }
39+ if out = append(out, q); len(out) == maxQuestions {
40+ return false
41+ }
42+ }
43+ return true
44+ }
45+ if !add(current) {
46+ return out
47+ }
48+ for _, sess := range k.st.all() {
49+ if k.rec != nil && sess.ID == k.rec.ID {
50+ continue // its questions are in current, as they stand now
51+ }
52+ if !add(sess.Messages) {
53+ break
54+ }
55+ }
56+ return out
57+}
58+
59+// question is the text a user message holds as the user typed it, and
60+// whether it is a question at all.
61+func question(m *ai.Message) (string, bool) {
62+ if m == nil || m.Role != ai.RoleUser || compact.IsSummary(m) {
63+ return "", false
64+ }
65+ text, _, _ := strings.Cut(m.Text(), "\n\n"+hooks.ContextLabel)
66+ text = strings.TrimSpace(text)
67+ if text == "" || engine.IsNote(text) {
68+ return "", false
69+ }
70+ return text, true
71+}
added internal/store/questions_test.go +103 -0
new file mode 100644
@@ -0,0 +1,103 @@
1+package store
2+
3+import (
4+ "encoding/json"
5+ "path/filepath"
6+ "reflect"
7+ "testing"
8+ "time"
9+
10+ "mm/internal/compact"
11+ "mm/internal/hooks"
12+
13+ "github.com/firebase/genkit/go/ai"
14+)
15+
16+// stored writes a session file as it would be after a save at `updated`:
17+// Save stamps the wall clock, which would make the order of two files saved
18+// in a row depend on the clock's resolution.
19+func stored(t *testing.T, k *Keeper, id string, updated time.Time, msgs []*ai.Message) {
20+ t.Helper()
21+ data, err := json.Marshal(&Session{ID: id, UpdatedAt: updated, Messages: msgs})
22+ if err != nil {
23+ t.Fatal(err)
24+ }
25+ if err := writeAtomic(filepath.Join(k.Dir(), id+".json"), data); err != nil {
26+ t.Fatal(err)
27+ }
28+}
29+
30+func newQuestionsKeeper(t *testing.T) *Keeper {
31+ t.Helper()
32+ withSessions(t, true)
33+ k := NewKeeper(t.TempDir())
34+ if err := k.Save(nil); err != nil { // creates the directory and the current file
35+ t.Fatal(err)
36+ }
37+ return k
38+}
39+
40+func TestQuestionsNilKeeperHasNothingToRecall(t *testing.T) {
41+ var k *Keeper
42+ if got := k.Questions(talk(nil, "hello")); got != nil {
43+ t.Errorf("nil keeper recalled %q", got)
44+ }
45+}
46+
47+// The conversation in progress comes first, newest first; then the other
48+// sessions, the most recently updated first. The current session's own file
49+// is not read again: the history in memory is the one that counts.
50+func TestQuestionsOrderCurrentThenEarlierSessions(t *testing.T) {
51+ k := newQuestionsKeeper(t)
52+ now := time.Now().UTC()
53+ stored(t, k, "20260101-000000-aaaaaaaa", now.Add(-2*time.Hour), talk(talk(nil, "oldest a"), "oldest b"))
54+ stored(t, k, "20260102-000000-bbbbbbbb", now.Add(-time.Hour), talk(nil, "yesterday"))
55+ stored(t, k, k.CurrentID(), now, talk(nil, "stale copy of the current file"))
56+
57+ current := talk(talk(session0(), "first today"), "second today")
58+ want := []string{"second today", "first today", "yesterday", "oldest b", "oldest a"}
59+ if got := k.Questions(current); !reflect.DeepEqual(got, want) {
60+ t.Errorf("Questions = %q, want %q", got, want)
61+ }
62+}
63+
64+// What the user never typed is not recalled; what a hook added to a question
65+// is cut off it; a question asked twice in a row comes back once.
66+func TestQuestionsSkipWhatTheUserDidNotType(t *testing.T) {
67+ k := newQuestionsKeeper(t)
68+ msgs := session0()
69+ msgs = append(msgs, compact.SummaryPair("notes", 4, 2, 0)...)
70+ msgs = talk(msgs, "fix the build")
71+ msgs = talk(msgs, "fix the build")
72+ msgs = talk(msgs, "[mm: your last tool call was not run: …]")
73+ msgs = talk(msgs, "[prompt blocked by a hook: secret]")
74+ msgs = talk(msgs, " ")
75+ msgs = talk(msgs, "run the tests\n\n"+hooks.ContextLabel+"branch: main")
76+ msgs = append(msgs, nil)
77+
78+ want := []string{"run the tests", "fix the build"}
79+ if got := k.Questions(msgs); !reflect.DeepEqual(got, want) {
80+ t.Errorf("Questions = %q, want %q", got, want)
81+ }
82+}
83+
84+func TestQuestionsAreCapped(t *testing.T) {
85+ k := newQuestionsKeeper(t)
86+ var current []*ai.Message
87+ for i := range maxQuestions + 10 {
88+ current = talk(current, "question "+string(rune('a'+i%26))+time.Duration(i).String())
89+ }
90+ stored(t, k, "20260101-000000-aaaaaaaa", time.Now().UTC(), talk(nil, "never reached"))
91+ got := k.Questions(current)
92+ if len(got) != maxQuestions {
93+ t.Fatalf("len = %d, want %d", len(got), maxQuestions)
94+ }
95+ if got[0] != current[len(current)-2].Text() {
96+ t.Errorf("first = %q, want the last question asked", got[0])
97+ }
98+}
99+
100+// session0 is a fresh history: the system prompt alone.
101+func session0() []*ai.Message {
102+ return []*ai.Message{ai.NewSystemTextMessage("system")}
103+}
new file mode 100644
@@ -0,0 +1,103 @@
1+package store
2+
3+import (
4+ "encoding/json"
5+ "path/filepath"
6+ "reflect"
7+ "testing"
8+ "time"
9+
10+ "mm/internal/compact"
11+ "mm/internal/hooks"
12+
13+ "github.com/firebase/genkit/go/ai"
14+)
15+
16+// stored writes a session file as it would be after a save at `updated`:
17+// Save stamps the wall clock, which would make the order of two files saved
18+// in a row depend on the clock's resolution.
19+func stored(t *testing.T, k *Keeper, id string, updated time.Time, msgs []*ai.Message) {
20+ t.Helper()
21+ data, err := json.Marshal(&Session{ID: id, UpdatedAt: updated, Messages: msgs})
22+ if err != nil {
23+ t.Fatal(err)
24+ }
25+ if err := writeAtomic(filepath.Join(k.Dir(), id+".json"), data); err != nil {
26+ t.Fatal(err)
27+ }
28+}
29+
30+func newQuestionsKeeper(t *testing.T) *Keeper {
31+ t.Helper()
32+ withSessions(t, true)
33+ k := NewKeeper(t.TempDir())
34+ if err := k.Save(nil); err != nil { // creates the directory and the current file
35+ t.Fatal(err)
36+ }
37+ return k
38+}
39+
40+func TestQuestionsNilKeeperHasNothingToRecall(t *testing.T) {
41+ var k *Keeper
42+ if got := k.Questions(talk(nil, "hello")); got != nil {
43+ t.Errorf("nil keeper recalled %q", got)
44+ }
45+}
46+
47+// The conversation in progress comes first, newest first; then the other
48+// sessions, the most recently updated first. The current session's own file
49+// is not read again: the history in memory is the one that counts.
50+func TestQuestionsOrderCurrentThenEarlierSessions(t *testing.T) {
51+ k := newQuestionsKeeper(t)
52+ now := time.Now().UTC()
53+ stored(t, k, "20260101-000000-aaaaaaaa", now.Add(-2*time.Hour), talk(talk(nil, "oldest a"), "oldest b"))
54+ stored(t, k, "20260102-000000-bbbbbbbb", now.Add(-time.Hour), talk(nil, "yesterday"))
55+ stored(t, k, k.CurrentID(), now, talk(nil, "stale copy of the current file"))
56+
57+ current := talk(talk(session0(), "first today"), "second today")
58+ want := []string{"second today", "first today", "yesterday", "oldest b", "oldest a"}
59+ if got := k.Questions(current); !reflect.DeepEqual(got, want) {
60+ t.Errorf("Questions = %q, want %q", got, want)
61+ }
62+}
63+
64+// What the user never typed is not recalled; what a hook added to a question
65+// is cut off it; a question asked twice in a row comes back once.
66+func TestQuestionsSkipWhatTheUserDidNotType(t *testing.T) {
67+ k := newQuestionsKeeper(t)
68+ msgs := session0()
69+ msgs = append(msgs, compact.SummaryPair("notes", 4, 2, 0)...)
70+ msgs = talk(msgs, "fix the build")
71+ msgs = talk(msgs, "fix the build")
72+ msgs = talk(msgs, "[mm: your last tool call was not run: …]")
73+ msgs = talk(msgs, "[prompt blocked by a hook: secret]")
74+ msgs = talk(msgs, " ")
75+ msgs = talk(msgs, "run the tests\n\n"+hooks.ContextLabel+"branch: main")
76+ msgs = append(msgs, nil)
77+
78+ want := []string{"run the tests", "fix the build"}
79+ if got := k.Questions(msgs); !reflect.DeepEqual(got, want) {
80+ t.Errorf("Questions = %q, want %q", got, want)
81+ }
82+}
83+
84+func TestQuestionsAreCapped(t *testing.T) {
85+ k := newQuestionsKeeper(t)
86+ var current []*ai.Message
87+ for i := range maxQuestions + 10 {
88+ current = talk(current, "question "+string(rune('a'+i%26))+time.Duration(i).String())
89+ }
90+ stored(t, k, "20260101-000000-aaaaaaaa", time.Now().UTC(), talk(nil, "never reached"))
91+ got := k.Questions(current)
92+ if len(got) != maxQuestions {
93+ t.Fatalf("len = %d, want %d", len(got), maxQuestions)
94+ }
95+ if got[0] != current[len(current)-2].Text() {
96+ t.Errorf("first = %q, want the last question asked", got[0])
97+ }
98+}
99+
100+// session0 is a fresh history: the system prompt alone.
101+func session0() []*ai.Message {
102+ return []*ai.Message{ai.NewSystemTextMessage("system")}
103+}
modified internal/store/store.go +12 -2
@@ -217,11 +217,21 @@ func (s *Store) Load(id string) (*Session, error) {
217217 // directory is an empty list; a file that cannot be decoded is skipped — one
218218 // corrupt session must not hide the others.
219219 func (s *Store) List() []Summary {
220+ var out []Summary
221+ for _, sess := range s.all() {
222+ out = append(out, summarize(sess))
223+ }
224+ return out
225+}
226+
227+// all loads every stored session, most recently updated first, with List's
228+// rules: a missing directory is no session, an unreadable file is skipped.
229+func (s *Store) all() []*Session {
220230 entries, err := os.ReadDir(s.dir)
221231 if err != nil {
222232 return nil
223233 }
224- var out []Summary
234+ var out []*Session
225235 for _, e := range entries {
226236 name := e.Name()
227237 if e.IsDir() || !strings.HasSuffix(name, ".json") {
@@ -231,7 +241,7 @@ func (s *Store) List() []Summary {
231241 if err != nil {
232242 continue
233243 }
234- out = append(out, summarize(sess))
244+ out = append(out, sess)
235245 }
236246 sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt.After(out[j].UpdatedAt) })
237247 return out
@@ -217,11 +217,21 @@ func (s *Store) Load(id string) (*Session, error) {
217 // directory is an empty list; a file that cannot be decoded is skipped — one217 // directory is an empty list; a file that cannot be decoded is skipped — one
218 // corrupt session must not hide the others.218 // corrupt session must not hide the others.
219 func (s *Store) List() []Summary {219 func (s *Store) List() []Summary {
220+ var out []Summary
221+ for _, sess := range s.all() {
222+ out = append(out, summarize(sess))
223+ }
224+ return out
225+}
226+
227+// all loads every stored session, most recently updated first, with List's
228+// rules: a missing directory is no session, an unreadable file is skipped.
229+func (s *Store) all() []*Session {
220 entries, err := os.ReadDir(s.dir)230 entries, err := os.ReadDir(s.dir)
221 if err != nil {231 if err != nil {
222 return nil232 return nil
223 }233 }
224- var out []Summary234+ var out []*Session
225 for _, e := range entries {235 for _, e := range entries {
226 name := e.Name()236 name := e.Name()
227 if e.IsDir() || !strings.HasSuffix(name, ".json") {237 if e.IsDir() || !strings.HasSuffix(name, ".json") {
@@ -231,7 +241,7 @@ func (s *Store) List() []Summary {
231 if err != nil {241 if err != nil {
232 continue242 continue
233 }243 }
234- out = append(out, summarize(sess))244+ out = append(out, sess)
235 }245 }
236 sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt.After(out[j].UpdatedAt) })246 sort.Slice(out, func(i, j int) bool { return out[i].UpdatedAt.After(out[j].UpdatedAt) })
237 return out247 return out
modified internal/tui/commands.go +1 -1
@@ -118,7 +118,7 @@ func (r *runner) unload(model string) {
118118
119119 // help lists the window's commands and keys.
120120 func (r *runner) help() {
121- r.t.Notice(command.Usage(command.Window) + "\n\nEnter sends · Ctrl-J new line · Esc cancels the turn · Ctrl-C cancels then quits (twice to leave a turn that will not stop) · Ctrl-L clears · Ctrl-Y copies the last answer · F2 mouse on/off (off: select text with the mouse as usual) · PgUp/PgDn scroll · Tab moves the focus · @path attaches a file")
121+ r.t.Notice(command.Usage(command.Window) + "\n\nEnter sends · Ctrl-J new line · Esc cancels the turn (idle: clears the box) · ↑/↓ recall earlier questions (sessions on) · Ctrl-C cancels then quits (twice to leave a turn that will not stop) · Ctrl-L clears · Ctrl-Y copies the last answer · F2 mouse on/off (off: select text with the mouse as usual) · PgUp/PgDn scroll · Tab moves the focus · @path attaches a file")
122122 }
123123
124124 // quitNow ends the loop on its next turn.
@@ -118,7 +118,7 @@ func (r *runner) unload(model string) {
118 118
119 // help lists the window's commands and keys.119 // help lists the window's commands and keys.
120 func (r *runner) help() {120 func (r *runner) help() {
121- r.t.Notice(command.Usage(command.Window) + "\n\nEnter sends · Ctrl-J new line · Esc cancels the turn · Ctrl-C cancels then quits (twice to leave a turn that will not stop) · Ctrl-L clears · Ctrl-Y copies the last answer · F2 mouse on/off (off: select text with the mouse as usual) · PgUp/PgDn scroll · Tab moves the focus · @path attaches a file")121+ r.t.Notice(command.Usage(command.Window) + "\n\nEnter sends · Ctrl-J new line · Esc cancels the turn (idle: clears the box) · ↑/↓ recall earlier questions (sessions on) · Ctrl-C cancels then quits (twice to leave a turn that will not stop) · Ctrl-L clears · Ctrl-Y copies the last answer · F2 mouse on/off (off: select text with the mouse as usual) · PgUp/PgDn scroll · Tab moves the focus · @path attaches a file")
122 }122 }
123 123
124 // quitNow ends the loop on its next turn.124 // quitNow ends the loop on its next turn.
modified internal/tui/events.go +20 -4
@@ -120,7 +120,7 @@ func (v *View) handleWindowKey(ev *tcell.EventKey) bool {
120120 v.on.quit()
121121 }
122122 case ev.Key() == tcell.KeyEscape:
123- v.abortTurn()
123+ v.escape()
124124 case ev.Key() == tcell.KeyCtrlL:
125125 v.clearScreen()
126126 case ev.Key() == tcell.KeyF1:
@@ -155,7 +155,21 @@ func (v *View) cancelTurn() bool {
155155 return v.on.cancel != nil && v.on.cancel()
156156 }
157157
158-// abortTurn is Escape: the same cancellation, with the runner saying what
158+// escape is Escape: it cancels the turn in progress; when none runs and the
159+// box holds something — a recalled question, a draft — it clears the box
160+// instead (ticket 68), so a look back costs one key to undo. An empty box
161+// with nothing running keeps the old answer: abort says there is nothing to
162+// stop.
163+func (v *View) escape() {
164+ if v.busy == "" && v.Input() != "" {
165+ v.setInput("")
166+ v.recall = recall{}
167+ return
168+ }
169+ v.abortTurn()
170+}
171+
172+// abortTurn is Escape during a turn: the same cancellation, with the runner saying what
159173 // happened — including that the turn is not listening any more.
160174 func (v *View) abortTurn() {
161175 if v.on.abort != nil {
@@ -183,9 +197,11 @@ func (v *View) handleInputKey(ev *tcell.EventKey) bool {
183197 case tcell.KeyEnd:
184198 v.column = len(v.runes())
185199 case tcell.KeyUp:
186- return v.moveRow(-1) // false on the first row: scroll the conversation instead
200+ // On the first row: the previous question (recall.go), else scroll
201+ // the conversation.
202+ return v.moveRow(-1) || v.recallStep(true)
187203 case tcell.KeyDown:
188- return v.moveRow(1)
204+ return v.moveRow(1) || v.recallStep(false)
189205 default:
190206 return false
191207 }
@@ -120,7 +120,7 @@ func (v *View) handleWindowKey(ev *tcell.EventKey) bool {
120 v.on.quit()120 v.on.quit()
121 }121 }
122 case ev.Key() == tcell.KeyEscape:122 case ev.Key() == tcell.KeyEscape:
123- v.abortTurn()123+ v.escape()
124 case ev.Key() == tcell.KeyCtrlL:124 case ev.Key() == tcell.KeyCtrlL:
125 v.clearScreen()125 v.clearScreen()
126 case ev.Key() == tcell.KeyF1:126 case ev.Key() == tcell.KeyF1:
@@ -155,7 +155,21 @@ func (v *View) cancelTurn() bool {
155 return v.on.cancel != nil && v.on.cancel()155 return v.on.cancel != nil && v.on.cancel()
156 }156 }
157 157
158-// abortTurn is Escape: the same cancellation, with the runner saying what158+// escape is Escape: it cancels the turn in progress; when none runs and the
159+// box holds something — a recalled question, a draft — it clears the box
160+// instead (ticket 68), so a look back costs one key to undo. An empty box
161+// with nothing running keeps the old answer: abort says there is nothing to
162+// stop.
163+func (v *View) escape() {
164+ if v.busy == "" && v.Input() != "" {
165+ v.setInput("")
166+ v.recall = recall{}
167+ return
168+ }
169+ v.abortTurn()
170+}
171+
172+// abortTurn is Escape during a turn: the same cancellation, with the runner saying what
159 // happened — including that the turn is not listening any more.173 // happened — including that the turn is not listening any more.
160 func (v *View) abortTurn() {174 func (v *View) abortTurn() {
161 if v.on.abort != nil {175 if v.on.abort != nil {
@@ -183,9 +197,11 @@ func (v *View) handleInputKey(ev *tcell.EventKey) bool {
183 case tcell.KeyEnd:197 case tcell.KeyEnd:
184 v.column = len(v.runes())198 v.column = len(v.runes())
185 case tcell.KeyUp:199 case tcell.KeyUp:
186- return v.moveRow(-1) // false on the first row: scroll the conversation instead200+ // On the first row: the previous question (recall.go), else scroll
201+ // the conversation.
202+ return v.moveRow(-1) || v.recallStep(true)
187 case tcell.KeyDown:203 case tcell.KeyDown:
188- return v.moveRow(1)204+ return v.moveRow(1) || v.recallStep(false)
189 default:205 default:
190 return false206 return false
191 }207 }
added internal/tui/recall.go +74 -0
new file mode 100644
@@ -0,0 +1,74 @@
1+package tui
2+
3+import "maps"
4+
5+// Recalling the questions already asked (ticket 68): with the focus on the
6+// box, ↑ on its first row puts the previous question in it, ↓ on its last
7+// row the next one, and ↓ past the newest gives back what was being typed
8+// before. The questions come from the runner (hooks.questions), which reads
9+// them from the stored sessions — so there is nothing to recall, and the
10+// arrows scroll the conversation as before, when sessions are off.
11+//
12+// A recalled question is ordinary text: edit it and send it, or press Escape
13+// to clear the box (events.go). Editing ends the walk: the next ↑ starts
14+// again from the newest question, with the edited text as the draft.
15+type recall struct {
16+ entries []string // newest first, as fetched when the walk started
17+ at int // index in entries of what the box shows; -1: the draft
18+ shown string // the box as the walk last left it, to notice an edit
19+ draft string // what was typed before the walk started
20+ pastes map[rune]paste
21+}
22+
23+// walking reports whether the box still shows what the walk put there.
24+func (rc *recall) walking(input string) bool {
25+ return rc.entries != nil && input == rc.shown
26+}
27+
28+// recallStep moves the walk one question older (↑) or newer (↓) and reports
29+// whether it used the key. It does not when there is nothing to recall, or
30+// on ↓ with no walk in progress: the arrow then scrolls the conversation.
31+// At the oldest question ↑ is used and does nothing, so the conversation
32+// does not jump under a question being looked at.
33+func (v *View) recallStep(older bool) bool {
34+ rc := &v.recall
35+ if !rc.walking(v.Input()) {
36+ if !older || !v.startRecall() {
37+ return false
38+ }
39+ }
40+ next := rc.at - 1
41+ if older {
42+ next = rc.at + 1
43+ }
44+ switch {
45+ case next >= len(rc.entries):
46+ return true
47+ case next < 0:
48+ v.setInput(rc.draft)
49+ v.pastes.blocks = maps.Clone(rc.pastes)
50+ v.recall = recall{}
51+ return true
52+ }
53+ rc.at = next
54+ v.setInput(rc.entries[next])
55+ rc.shown = v.Input()
56+ // A question that ends on "@…" or "$…" would open the picker, and the
57+ // picker takes the arrows: the walk must keep them.
58+ v.pick.dismiss(rc.shown)
59+ return true
60+}
61+
62+// startRecall fetches the questions and keeps the draft aside; false when
63+// there is nothing to recall.
64+func (v *View) startRecall() bool {
65+ if v.on.questions == nil {
66+ return false
67+ }
68+ entries := v.on.questions()
69+ if len(entries) == 0 {
70+ return false
71+ }
72+ v.recall = recall{entries: entries, at: -1, draft: v.Input(), pastes: maps.Clone(v.pastes.blocks)}
73+ return true
74+}
new file mode 100644
@@ -0,0 +1,74 @@
1+package tui
2+
3+import "maps"
4+
5+// Recalling the questions already asked (ticket 68): with the focus on the
6+// box, ↑ on its first row puts the previous question in it, ↓ on its last
7+// row the next one, and ↓ past the newest gives back what was being typed
8+// before. The questions come from the runner (hooks.questions), which reads
9+// them from the stored sessions — so there is nothing to recall, and the
10+// arrows scroll the conversation as before, when sessions are off.
11+//
12+// A recalled question is ordinary text: edit it and send it, or press Escape
13+// to clear the box (events.go). Editing ends the walk: the next ↑ starts
14+// again from the newest question, with the edited text as the draft.
15+type recall struct {
16+ entries []string // newest first, as fetched when the walk started
17+ at int // index in entries of what the box shows; -1: the draft
18+ shown string // the box as the walk last left it, to notice an edit
19+ draft string // what was typed before the walk started
20+ pastes map[rune]paste
21+}
22+
23+// walking reports whether the box still shows what the walk put there.
24+func (rc *recall) walking(input string) bool {
25+ return rc.entries != nil && input == rc.shown
26+}
27+
28+// recallStep moves the walk one question older (↑) or newer (↓) and reports
29+// whether it used the key. It does not when there is nothing to recall, or
30+// on ↓ with no walk in progress: the arrow then scrolls the conversation.
31+// At the oldest question ↑ is used and does nothing, so the conversation
32+// does not jump under a question being looked at.
33+func (v *View) recallStep(older bool) bool {
34+ rc := &v.recall
35+ if !rc.walking(v.Input()) {
36+ if !older || !v.startRecall() {
37+ return false
38+ }
39+ }
40+ next := rc.at - 1
41+ if older {
42+ next = rc.at + 1
43+ }
44+ switch {
45+ case next >= len(rc.entries):
46+ return true
47+ case next < 0:
48+ v.setInput(rc.draft)
49+ v.pastes.blocks = maps.Clone(rc.pastes)
50+ v.recall = recall{}
51+ return true
52+ }
53+ rc.at = next
54+ v.setInput(rc.entries[next])
55+ rc.shown = v.Input()
56+ // A question that ends on "@…" or "$…" would open the picker, and the
57+ // picker takes the arrows: the walk must keep them.
58+ v.pick.dismiss(rc.shown)
59+ return true
60+}
61+
62+// startRecall fetches the questions and keeps the draft aside; false when
63+// there is nothing to recall.
64+func (v *View) startRecall() bool {
65+ if v.on.questions == nil {
66+ return false
67+ }
68+ entries := v.on.questions()
69+ if len(entries) == 0 {
70+ return false
71+ }
72+ v.recall = recall{entries: entries, at: -1, draft: v.Input(), pastes: maps.Clone(v.pastes.blocks)}
73+ return true
74+}
added internal/tui/recall_test.go +226 -0
new file mode 100644
@@ -0,0 +1,226 @@
1+package tui
2+
3+import (
4+ "strings"
5+ "testing"
6+
7+ "mm/internal/config"
8+ "mm/internal/store"
9+
10+ "github.com/gdamore/tcell/v2"
11+)
12+
13+// recallHarness is a window whose ↑ recalls the given questions, newest
14+// first, and counts how often it asked for them.
15+func recallHarness(t *testing.T, questions ...string) (*harness, *int) {
16+ t.Helper()
17+ hs := newHarness(t, 60, 12)
18+ asked := 0
19+ hs.v.on.questions = func() []string { asked++; return questions }
20+ return hs, &asked
21+}
22+
23+func (hs *harness) up() { hs.key(tcell.KeyUp, tcell.ModNone) }
24+func (hs *harness) down() { hs.key(tcell.KeyDown, tcell.ModNone) }
25+
26+// ↑ walks back through the questions, ↓ forward, and ↓ past the newest gives
27+// back what was being typed — the draft is not lost to a look back.
28+func TestRecallWalksBackAndForthAndRestoresTheDraft(t *testing.T) {
29+ hs, asked := recallHarness(t, "newest", "middle", "oldest")
30+ hs.typeText("half a thought")
31+
32+ for _, want := range []string{"newest", "middle", "oldest", "oldest"} {
33+ hs.up()
34+ if got := hs.v.Input(); got != want {
35+ t.Fatalf("after ↑: box = %q, want %q", got, want)
36+ }
37+ }
38+ for _, want := range []string{"middle", "newest", "half a thought"} {
39+ hs.down()
40+ if got := hs.v.Input(); got != want {
41+ t.Fatalf("after ↓: box = %q, want %q", got, want)
42+ }
43+ }
44+ if *asked != 1 {
45+ t.Errorf("questions fetched %d times during one walk, want 1", *asked)
46+ }
47+ if hs.v.column != len([]rune("half a thought")) {
48+ t.Errorf("cursor at column %d, want the end of the draft", hs.v.column)
49+ }
50+}
51+
52+// A recalled question can be edited and sent like anything typed; the walk
53+// ends with the send, and the next ↑ starts from the newest question again.
54+func TestRecalledQuestionIsEditedAndSent(t *testing.T) {
55+ hs, asked := recallHarness(t, "run the tests", "fix the build")
56+ hs.up()
57+ hs.up()
58+ hs.typeText(" again")
59+ hs.key(tcell.KeyEnter, tcell.ModNone)
60+ if len(hs.sent) != 1 || hs.sent[0] != "fix the build again" {
61+ t.Fatalf("sent %q", hs.sent)
62+ }
63+ hs.up()
64+ if got := hs.v.Input(); got != "run the tests" {
65+ t.Errorf("after the send, ↑ shows %q, want the newest question", got)
66+ }
67+ if *asked != 2 {
68+ t.Errorf("questions fetched %d times, want once per walk (2)", *asked)
69+ }
70+}
71+
72+// Editing a recalled question ends the walk: ↑ then starts over from the
73+// newest question, and ↓ past it gives back the edited text, not the old
74+// draft.
75+func TestEditingEndsTheWalk(t *testing.T) {
76+ hs, _ := recallHarness(t, "newest", "oldest")
77+ hs.up()
78+ hs.up()
79+ hs.typeText("!")
80+ hs.up()
81+ if got := hs.v.Input(); got != "newest" {
82+ t.Fatalf("after an edit, ↑ shows %q, want the newest question", got)
83+ }
84+ hs.down()
85+ if got := hs.v.Input(); got != "oldest!" {
86+ t.Errorf("↓ past the newest gives %q, want the edited text", got)
87+ }
88+}
89+
90+// Nothing to recall — sessions off, or a project with no question yet — and
91+// the arrows keep their old meaning: the conversation scrolls, the box stays.
92+func TestNothingToRecallScrollsTheConversation(t *testing.T) {
93+ hs, _ := recallHarness(t)
94+ for i := range 40 {
95+ hs.v.t.Notice(strings.Repeat("line ", 3) + string(rune('a'+i%26)))
96+ }
97+ hs.rows()
98+ hs.typeText("draft")
99+ hs.up()
100+ if hs.v.Input() != "draft" {
101+ t.Errorf("box = %q, want the draft untouched", hs.v.Input())
102+ }
103+ if hs.v.follow {
104+ t.Error("↑ with nothing to recall did not scroll the conversation up")
105+ }
106+}
107+
108+// ↓ with no walk in progress scrolls, as before: it never recalls.
109+func TestDownWithoutAWalkDoesNotRecall(t *testing.T) {
110+ hs, asked := recallHarness(t, "newest")
111+ hs.down()
112+ if hs.v.Input() != "" || *asked != 0 {
113+ t.Errorf("↓ alone recalled: box %q, fetched %d", hs.v.Input(), *asked)
114+ }
115+}
116+
117+// A multi-line question is walked row by row before the walk moves on: ↑ on
118+// its last row goes up inside it, and only its first row recalls the next.
119+func TestMultiLineQuestionIsWalkedRowByRow(t *testing.T) {
120+ hs, _ := recallHarness(t, "line one\nline two", "older")
121+ hs.up()
122+ if hs.v.Input() != "line one\nline two" || hs.v.cursor != 1 {
123+ t.Fatalf("box %q, cursor on line %d", hs.v.Input(), hs.v.cursor)
124+ }
125+ hs.up()
126+ if hs.v.Input() != "line one\nline two" || hs.v.cursor != 0 {
127+ t.Fatalf("↑ on the last row: box %q, cursor on line %d", hs.v.Input(), hs.v.cursor)
128+ }
129+ hs.up()
130+ if hs.v.Input() != "older" {
131+ t.Errorf("↑ on the first row: box %q, want the older question", hs.v.Input())
132+ }
133+}
134+
135+// A question that ends on a mention would open the picker, which takes the
136+// arrows; the walk keeps them.
137+func TestRecalledMentionDoesNotOpenThePicker(t *testing.T) {
138+ hs, _ := recallHarness(t, "read @", "older")
139+ hs.up()
140+ if hs.v.pickerOpen() {
141+ t.Fatal("the picker opened on a recalled question")
142+ }
143+ hs.up()
144+ if hs.v.Input() != "older" {
145+ t.Errorf("box %q, want the older question", hs.v.Input())
146+ }
147+}
148+
149+// A draft holding a collapsed paste gets it back, token and text.
150+func TestDraftPasteSurvivesTheWalk(t *testing.T) {
151+ hs, _ := recallHarness(t, "newest")
152+ long := strings.Repeat("pasted line\n", 5)
153+ hs.paste(long)
154+ hs.up()
155+ hs.down()
156+ hs.key(tcell.KeyEnter, tcell.ModNone)
157+ if len(hs.sent) != 1 || hs.sent[0] != long {
158+ t.Errorf("sent %q, want the pasted text", hs.sent)
159+ }
160+}
161+
162+// The runner's side: with sessions on, the question of the turn in progress
163+// is the first one recalled, then the history's; with sessions off, nothing.
164+func TestRunnerQuestions(t *testing.T) {
165+ r, _ := newTestRunner(t, &fakeModel{answer: "ok", blockFirst: true})
166+ if got := r.questions(); got != nil {
167+ t.Errorf("sessions off: questions = %q", got)
168+ }
169+ config.Cfg.Sessions = config.SessionsConfig{Enabled: true, Dir: ".mm/sessions"}
170+ r.keep = store.NewKeeper(r.opts.Cwd)
171+
172+ r.submit(promptOf("first"))
173+ if got := r.questions(); len(got) != 1 || got[0] != "first" {
174+ t.Errorf("during the turn: questions = %q, want the question in flight", got)
175+ }
176+ r.cancelTurn()
177+ waitIdle(t, r)
178+ // The cancelled turn left no trace in the history; its question can
179+ // still be recalled, to retry it.
180+ if got := r.questions(); len(got) != 1 || got[0] != "first" {
181+ t.Errorf("after a cancelled turn: questions = %q, want it still there", got)
182+ }
183+ r.submit(promptOf("first"))
184+ waitIdle(t, r)
185+ r.submit(promptOf("second"))
186+ waitIdle(t, r)
187+ if got := r.questions(); len(got) != 2 || got[0] != "second" || got[1] != "first" {
188+ t.Errorf("after two turns: questions = %q", got)
189+ }
190+}
191+
192+// Escape clears the box when no turn runs — a recalled question or a draft,
193+// pastes with it — and ends the walk; during a turn it still cancels, the
194+// box untouched.
195+func TestEscapeClearsTheBoxWhenIdle(t *testing.T) {
196+ hs, _ := recallHarness(t, "newest", "oldest")
197+ aborted := 0
198+ hs.v.on.abort = func() { aborted++ }
199+
200+ hs.paste(strings.Repeat("pasted line\n", 5))
201+ hs.up()
202+ hs.key(tcell.KeyEscape, tcell.ModNone)
203+ if hs.v.Input() != "" || aborted != 0 {
204+ t.Fatalf("idle Escape: box %q, aborted %d", hs.v.Input(), aborted)
205+ }
206+ if len(hs.v.pastes.blocks) != 0 {
207+ t.Errorf("idle Escape kept %d paste(s)", len(hs.v.pastes.blocks))
208+ }
209+ hs.up()
210+ if hs.v.Input() != "newest" {
211+ t.Errorf("after Escape, ↑ shows %q, want a fresh walk from the newest", hs.v.Input())
212+ }
213+
214+ hs.key(tcell.KeyEscape, tcell.ModNone)
215+ hs.key(tcell.KeyEscape, tcell.ModNone) // empty box, nothing running: the old answer
216+ if aborted != 1 {
217+ t.Errorf("Escape on an empty idle box: aborted %d, want 1", aborted)
218+ }
219+
220+ hs.v.busy = "thinking"
221+ hs.typeText("queued")
222+ hs.key(tcell.KeyEscape, tcell.ModNone)
223+ if hs.v.Input() != "queued" || aborted != 2 {
224+ t.Errorf("Escape during a turn: box %q, aborted %d", hs.v.Input(), aborted)
225+ }
226+}
new file mode 100644
@@ -0,0 +1,226 @@
1+package tui
2+
3+import (
4+ "strings"
5+ "testing"
6+
7+ "mm/internal/config"
8+ "mm/internal/store"
9+
10+ "github.com/gdamore/tcell/v2"
11+)
12+
13+// recallHarness is a window whose ↑ recalls the given questions, newest
14+// first, and counts how often it asked for them.
15+func recallHarness(t *testing.T, questions ...string) (*harness, *int) {
16+ t.Helper()
17+ hs := newHarness(t, 60, 12)
18+ asked := 0
19+ hs.v.on.questions = func() []string { asked++; return questions }
20+ return hs, &asked
21+}
22+
23+func (hs *harness) up() { hs.key(tcell.KeyUp, tcell.ModNone) }
24+func (hs *harness) down() { hs.key(tcell.KeyDown, tcell.ModNone) }
25+
26+// ↑ walks back through the questions, ↓ forward, and ↓ past the newest gives
27+// back what was being typed — the draft is not lost to a look back.
28+func TestRecallWalksBackAndForthAndRestoresTheDraft(t *testing.T) {
29+ hs, asked := recallHarness(t, "newest", "middle", "oldest")
30+ hs.typeText("half a thought")
31+
32+ for _, want := range []string{"newest", "middle", "oldest", "oldest"} {
33+ hs.up()
34+ if got := hs.v.Input(); got != want {
35+ t.Fatalf("after ↑: box = %q, want %q", got, want)
36+ }
37+ }
38+ for _, want := range []string{"middle", "newest", "half a thought"} {
39+ hs.down()
40+ if got := hs.v.Input(); got != want {
41+ t.Fatalf("after ↓: box = %q, want %q", got, want)
42+ }
43+ }
44+ if *asked != 1 {
45+ t.Errorf("questions fetched %d times during one walk, want 1", *asked)
46+ }
47+ if hs.v.column != len([]rune("half a thought")) {
48+ t.Errorf("cursor at column %d, want the end of the draft", hs.v.column)
49+ }
50+}
51+
52+// A recalled question can be edited and sent like anything typed; the walk
53+// ends with the send, and the next ↑ starts from the newest question again.
54+func TestRecalledQuestionIsEditedAndSent(t *testing.T) {
55+ hs, asked := recallHarness(t, "run the tests", "fix the build")
56+ hs.up()
57+ hs.up()
58+ hs.typeText(" again")
59+ hs.key(tcell.KeyEnter, tcell.ModNone)
60+ if len(hs.sent) != 1 || hs.sent[0] != "fix the build again" {
61+ t.Fatalf("sent %q", hs.sent)
62+ }
63+ hs.up()
64+ if got := hs.v.Input(); got != "run the tests" {
65+ t.Errorf("after the send, ↑ shows %q, want the newest question", got)
66+ }
67+ if *asked != 2 {
68+ t.Errorf("questions fetched %d times, want once per walk (2)", *asked)
69+ }
70+}
71+
72+// Editing a recalled question ends the walk: ↑ then starts over from the
73+// newest question, and ↓ past it gives back the edited text, not the old
74+// draft.
75+func TestEditingEndsTheWalk(t *testing.T) {
76+ hs, _ := recallHarness(t, "newest", "oldest")
77+ hs.up()
78+ hs.up()
79+ hs.typeText("!")
80+ hs.up()
81+ if got := hs.v.Input(); got != "newest" {
82+ t.Fatalf("after an edit, ↑ shows %q, want the newest question", got)
83+ }
84+ hs.down()
85+ if got := hs.v.Input(); got != "oldest!" {
86+ t.Errorf("↓ past the newest gives %q, want the edited text", got)
87+ }
88+}
89+
90+// Nothing to recall — sessions off, or a project with no question yet — and
91+// the arrows keep their old meaning: the conversation scrolls, the box stays.
92+func TestNothingToRecallScrollsTheConversation(t *testing.T) {
93+ hs, _ := recallHarness(t)
94+ for i := range 40 {
95+ hs.v.t.Notice(strings.Repeat("line ", 3) + string(rune('a'+i%26)))
96+ }
97+ hs.rows()
98+ hs.typeText("draft")
99+ hs.up()
100+ if hs.v.Input() != "draft" {
101+ t.Errorf("box = %q, want the draft untouched", hs.v.Input())
102+ }
103+ if hs.v.follow {
104+ t.Error("↑ with nothing to recall did not scroll the conversation up")
105+ }
106+}
107+
108+// ↓ with no walk in progress scrolls, as before: it never recalls.
109+func TestDownWithoutAWalkDoesNotRecall(t *testing.T) {
110+ hs, asked := recallHarness(t, "newest")
111+ hs.down()
112+ if hs.v.Input() != "" || *asked != 0 {
113+ t.Errorf("↓ alone recalled: box %q, fetched %d", hs.v.Input(), *asked)
114+ }
115+}
116+
117+// A multi-line question is walked row by row before the walk moves on: ↑ on
118+// its last row goes up inside it, and only its first row recalls the next.
119+func TestMultiLineQuestionIsWalkedRowByRow(t *testing.T) {
120+ hs, _ := recallHarness(t, "line one\nline two", "older")
121+ hs.up()
122+ if hs.v.Input() != "line one\nline two" || hs.v.cursor != 1 {
123+ t.Fatalf("box %q, cursor on line %d", hs.v.Input(), hs.v.cursor)
124+ }
125+ hs.up()
126+ if hs.v.Input() != "line one\nline two" || hs.v.cursor != 0 {
127+ t.Fatalf("↑ on the last row: box %q, cursor on line %d", hs.v.Input(), hs.v.cursor)
128+ }
129+ hs.up()
130+ if hs.v.Input() != "older" {
131+ t.Errorf("↑ on the first row: box %q, want the older question", hs.v.Input())
132+ }
133+}
134+
135+// A question that ends on a mention would open the picker, which takes the
136+// arrows; the walk keeps them.
137+func TestRecalledMentionDoesNotOpenThePicker(t *testing.T) {
138+ hs, _ := recallHarness(t, "read @", "older")
139+ hs.up()
140+ if hs.v.pickerOpen() {
141+ t.Fatal("the picker opened on a recalled question")
142+ }
143+ hs.up()
144+ if hs.v.Input() != "older" {
145+ t.Errorf("box %q, want the older question", hs.v.Input())
146+ }
147+}
148+
149+// A draft holding a collapsed paste gets it back, token and text.
150+func TestDraftPasteSurvivesTheWalk(t *testing.T) {
151+ hs, _ := recallHarness(t, "newest")
152+ long := strings.Repeat("pasted line\n", 5)
153+ hs.paste(long)
154+ hs.up()
155+ hs.down()
156+ hs.key(tcell.KeyEnter, tcell.ModNone)
157+ if len(hs.sent) != 1 || hs.sent[0] != long {
158+ t.Errorf("sent %q, want the pasted text", hs.sent)
159+ }
160+}
161+
162+// The runner's side: with sessions on, the question of the turn in progress
163+// is the first one recalled, then the history's; with sessions off, nothing.
164+func TestRunnerQuestions(t *testing.T) {
165+ r, _ := newTestRunner(t, &fakeModel{answer: "ok", blockFirst: true})
166+ if got := r.questions(); got != nil {
167+ t.Errorf("sessions off: questions = %q", got)
168+ }
169+ config.Cfg.Sessions = config.SessionsConfig{Enabled: true, Dir: ".mm/sessions"}
170+ r.keep = store.NewKeeper(r.opts.Cwd)
171+
172+ r.submit(promptOf("first"))
173+ if got := r.questions(); len(got) != 1 || got[0] != "first" {
174+ t.Errorf("during the turn: questions = %q, want the question in flight", got)
175+ }
176+ r.cancelTurn()
177+ waitIdle(t, r)
178+ // The cancelled turn left no trace in the history; its question can
179+ // still be recalled, to retry it.
180+ if got := r.questions(); len(got) != 1 || got[0] != "first" {
181+ t.Errorf("after a cancelled turn: questions = %q, want it still there", got)
182+ }
183+ r.submit(promptOf("first"))
184+ waitIdle(t, r)
185+ r.submit(promptOf("second"))
186+ waitIdle(t, r)
187+ if got := r.questions(); len(got) != 2 || got[0] != "second" || got[1] != "first" {
188+ t.Errorf("after two turns: questions = %q", got)
189+ }
190+}
191+
192+// Escape clears the box when no turn runs — a recalled question or a draft,
193+// pastes with it — and ends the walk; during a turn it still cancels, the
194+// box untouched.
195+func TestEscapeClearsTheBoxWhenIdle(t *testing.T) {
196+ hs, _ := recallHarness(t, "newest", "oldest")
197+ aborted := 0
198+ hs.v.on.abort = func() { aborted++ }
199+
200+ hs.paste(strings.Repeat("pasted line\n", 5))
201+ hs.up()
202+ hs.key(tcell.KeyEscape, tcell.ModNone)
203+ if hs.v.Input() != "" || aborted != 0 {
204+ t.Fatalf("idle Escape: box %q, aborted %d", hs.v.Input(), aborted)
205+ }
206+ if len(hs.v.pastes.blocks) != 0 {
207+ t.Errorf("idle Escape kept %d paste(s)", len(hs.v.pastes.blocks))
208+ }
209+ hs.up()
210+ if hs.v.Input() != "newest" {
211+ t.Errorf("after Escape, ↑ shows %q, want a fresh walk from the newest", hs.v.Input())
212+ }
213+
214+ hs.key(tcell.KeyEscape, tcell.ModNone)
215+ hs.key(tcell.KeyEscape, tcell.ModNone) // empty box, nothing running: the old answer
216+ if aborted != 1 {
217+ t.Errorf("Escape on an empty idle box: aborted %d, want 1", aborted)
218+ }
219+
220+ hs.v.busy = "thinking"
221+ hs.typeText("queued")
222+ hs.key(tcell.KeyEscape, tcell.ModNone)
223+ if hs.v.Input() != "queued" || aborted != 2 {
224+ t.Errorf("Escape during a turn: box %q, aborted %d", hs.v.Input(), aborted)
225+ }
226+}
modified internal/tui/tui.go +1 -0
@@ -73,6 +73,7 @@ func newApp(screen tcell.Screen, opts Options) *app {
7373 answer: r.answer,
7474 answerForm: r.answerForm,
7575 copy: func() { r.copy("") },
76+ questions: r.questions,
7677 })
7778 v.previewLines = config.Cfg.PreviewLines
7879 v.skills, v.cwd = opts.Skills, opts.Cwd
@@ -73,6 +73,7 @@ func newApp(screen tcell.Screen, opts Options) *app {
73 answer: r.answer,73 answer: r.answer,
74 answerForm: r.answerForm,74 answerForm: r.answerForm,
75 copy: func() { r.copy("") },75 copy: func() { r.copy("") },
76+ questions: r.questions,
76 })77 })
77 v.previewLines = config.Cfg.PreviewLines78 v.previewLines = config.Cfg.PreviewLines
78 v.skills, v.cwd = opts.Skills, opts.Cwd79 v.skills, v.cwd = opts.Skills, opts.Cwd
modified internal/tui/turn.go +22 -0
@@ -3,6 +3,7 @@ package tui
33 import (
44 "context"
55 "fmt"
6+ "slices"
67 "strings"
78 "sync"
89 "sync/atomic"
@@ -114,6 +115,12 @@ type runner struct {
114115 cancelled atomic.Bool
115116 results chan outcome
116117 pending Prompt // input to run once the cancelled turn has settled
118+ // asked is the question of the turn in progress, as the model reads it:
119+ // not in messages until the turn settles, yet the first one ↑ recalls.
120+ // A turn that fails before answering drops its question from the
121+ // history (session.AfterTurn); it stays here until the next question,
122+ // so ↑ can bring it back to retry.
123+ asked string
117124
118125 askMu sync.Mutex
119126 ask *permission
@@ -166,6 +173,7 @@ func (r *runner) startTurn(p Prompt) {
166173 // whatever procedure it happens to name.
167174 expanded, refs := skills.ExpandRefs(expanded, r.opts.Skills)
168175 input := p.Expand(expanded)
176+ r.asked = input
169177 for _, ref := range refs {
170178 r.t.Notice("📖 $" + ref.Name)
171179 }
@@ -292,6 +300,9 @@ func (r *runner) apply(res outcome) {
292300 return
293301 }
294302 r.messages = session.AfterTurn(res.messages, res.before, res.resp, res.history, res.err)
303+ if len(r.messages) >= res.before {
304+ r.asked = "" // in the history now; a failed turn's is not, and stays recallable
305+ }
295306 r.save()
296307 if res.compactFailed != "" {
297308 r.record(store.NewFailure(res.messages, store.FailCompaction, res.compactFailed, nil))
@@ -378,6 +389,17 @@ func (r *runner) record(f store.Failure) {
378389 }
379390 }
380391
392+// questions is what ↑ recalls in the box (recall.go): the questions of
393+// this session, the one in progress first, then the stored sessions'. None
394+// when sessions are off.
395+func (r *runner) questions() []string {
396+ msgs := r.messages
397+ if r.asked != "" {
398+ msgs = append(slices.Clone(msgs), ai.NewUserTextMessage(r.asked))
399+ }
400+ return r.keep.Questions(msgs)
401+}
402+
381403 // header is the top line: who is answering, how full the context is, which
382404 // session is being saved. During an /ask, the agent asked is the one
383405 // answering, so it names that agent and its model instead.
@@ -3,6 +3,7 @@ package tui
3 import (3 import (
4 "context"4 "context"
5 "fmt"5 "fmt"
6+ "slices"
6 "strings"7 "strings"
7 "sync"8 "sync"
8 "sync/atomic"9 "sync/atomic"
@@ -114,6 +115,12 @@ type runner struct {
114 cancelled atomic.Bool115 cancelled atomic.Bool
115 results chan outcome116 results chan outcome
116 pending Prompt // input to run once the cancelled turn has settled117 pending Prompt // input to run once the cancelled turn has settled
118+ // asked is the question of the turn in progress, as the model reads it:
119+ // not in messages until the turn settles, yet the first one ↑ recalls.
120+ // A turn that fails before answering drops its question from the
121+ // history (session.AfterTurn); it stays here until the next question,
122+ // so ↑ can bring it back to retry.
123+ asked string
117 124
118 askMu sync.Mutex125 askMu sync.Mutex
119 ask *permission126 ask *permission
@@ -166,6 +173,7 @@ func (r *runner) startTurn(p Prompt) {
166 // whatever procedure it happens to name.173 // whatever procedure it happens to name.
167 expanded, refs := skills.ExpandRefs(expanded, r.opts.Skills)174 expanded, refs := skills.ExpandRefs(expanded, r.opts.Skills)
168 input := p.Expand(expanded)175 input := p.Expand(expanded)
176+ r.asked = input
169 for _, ref := range refs {177 for _, ref := range refs {
170 r.t.Notice("📖 $" + ref.Name)178 r.t.Notice("📖 $" + ref.Name)
171 }179 }
@@ -292,6 +300,9 @@ func (r *runner) apply(res outcome) {
292 return300 return
293 }301 }
294 r.messages = session.AfterTurn(res.messages, res.before, res.resp, res.history, res.err)302 r.messages = session.AfterTurn(res.messages, res.before, res.resp, res.history, res.err)
303+ if len(r.messages) >= res.before {
304+ r.asked = "" // in the history now; a failed turn's is not, and stays recallable
305+ }
295 r.save()306 r.save()
296 if res.compactFailed != "" {307 if res.compactFailed != "" {
297 r.record(store.NewFailure(res.messages, store.FailCompaction, res.compactFailed, nil))308 r.record(store.NewFailure(res.messages, store.FailCompaction, res.compactFailed, nil))
@@ -378,6 +389,17 @@ func (r *runner) record(f store.Failure) {
378 }389 }
379 }390 }
380 391
392+// questions is what ↑ recalls in the box (recall.go): the questions of
393+// this session, the one in progress first, then the stored sessions'. None
394+// when sessions are off.
395+func (r *runner) questions() []string {
396+ msgs := r.messages
397+ if r.asked != "" {
398+ msgs = append(slices.Clone(msgs), ai.NewUserTextMessage(r.asked))
399+ }
400+ return r.keep.Questions(msgs)
401+}
402+
381 // header is the top line: who is answering, how full the context is, which403 // header is the top line: who is answering, how full the context is, which
382 // session is being saved. During an /ask, the agent asked is the one404 // session is being saved. During an /ask, the agent asked is the one
383 // answering, so it names that agent and its model instead.405 // answering, so it names that agent and its model instead.
modified internal/tui/view.go +7 -2
@@ -62,6 +62,9 @@ type hooks struct {
6262 answerForm func(elicit.Result)
6363 // copy is Ctrl-Y: the last answer to the clipboard.
6464 copy func()
65+ // questions is what ↑ recalls in the box, newest first (recall.go);
66+ // empty when sessions are off.
67+ questions func() []string
6568 }
6669
6770 // View is the window: header, conversation, rule, input box, status bar,
@@ -107,8 +110,9 @@ type View struct {
107110 // Off by default so the terminal's own text selection keeps working.
108111 mouse bool
109112
110- pick picker
111- now func() time.Time
113+ pick picker
114+ recall recall
115+ now func() time.Time
112116 }
113117
114118 // newView returns a window drawing on screen, reading t.
@@ -150,6 +154,7 @@ func (v *View) send() {
150154 }
151155 prompt := v.pastes.snapshot(text)
152156 v.setInput("")
157+ v.recall = recall{}
153158 v.follow = true
154159 v.pick.dismiss("")
155160 if v.on.send != nil {
@@ -62,6 +62,9 @@ type hooks struct {
62 answerForm func(elicit.Result)62 answerForm func(elicit.Result)
63 // copy is Ctrl-Y: the last answer to the clipboard.63 // copy is Ctrl-Y: the last answer to the clipboard.
64 copy func()64 copy func()
65+ // questions is what ↑ recalls in the box, newest first (recall.go);
66+ // empty when sessions are off.
67+ questions func() []string
65 }68 }
66 69
67 // View is the window: header, conversation, rule, input box, status bar,70 // View is the window: header, conversation, rule, input box, status bar,
@@ -107,8 +110,9 @@ type View struct {
107 // Off by default so the terminal's own text selection keeps working.110 // Off by default so the terminal's own text selection keeps working.
108 mouse bool111 mouse bool
109 112
110- pick picker113+ pick picker
111- now func() time.Time114+ recall recall
115+ now func() time.Time
112 }116 }
113 117
114 // newView returns a window drawing on screen, reading t.118 // newView returns a window drawing on screen, reading t.
@@ -150,6 +154,7 @@ func (v *View) send() {
150 }154 }
151 prompt := v.pastes.snapshot(text)155 prompt := v.pastes.snapshot(text)
152 v.setInput("")156 v.setInput("")
157+ v.recall = recall{}
153 v.follow = true158 v.follow = true
154 v.pick.dismiss("")159 v.pick.dismiss("")
155 if v.on.send != nil {160 if v.on.send != nil {
added release-notes/README.md +33 -0
new file mode 100644
@@ -0,0 +1,33 @@
1+# Release notes
2+
3+One note per tag, in English (`.en.md`) and French (`.fr.md`). Rebuilt on 2026-10-18 from the diffs between tags, the tickets and the project history: the commit messages say nothing.
4+
5+| Version | Date | Summary | |
6+|---|---|---|---|
7+| [v0.7.0](v0.7.0.en.md) | 2026-10-18 | Free a model's memory, see which hooks ran, pick up an earlier session from your editor, and screen questions with guard models. | [FR](v0.7.0.fr.md) |
8+| [v0.6.0](v0.6.0.en.md) | 2026-10-17 | Your own tools declared in the agent file, and answers saved to a file without being written again. | [FR](v0.6.0.fr.md) |
9+| [v0.5.0](v0.5.0.en.md) | 2026-10-16 | Hooks: your own scripts around generations, tool calls and calls to other agents. | [FR](v0.5.0.fr.md) |
10+| [v0.4.0](v0.4.0.en.md) | 2026-10-12 | The agent can ask you questions, MCP tools come with their parameters, and the demos move under `demos/`. | [FR](v0.4.0.fr.md) |
11+| [v0.3.0](v0.3.0.en.md) | 2026-10-09 | Yolo mode, Genkit 1.13.1, an `ask_agent` tool, traces of model traffic, JSON answers checked against Go types, and `AGENTS.md`. | [FR](v0.3.0.fr.md) |
12+| [v0.2.6](v0.2.6.en.md) | 2026-10-03 | An agent can ask another mm agent. | [FR](v0.2.6.fr.md) |
13+| [v0.2.5](v0.2.5.en.md) | 2026-10-01 | Two new model servers: llmman and Ollama. | [FR](v0.2.5.fr.md) |
14+| [v0.2.4](v0.2.4.en.md) | 2026-09-30 | The server's prompt cache works again from one question to the next. | [FR](v0.2.4.fr.md) |
15+| [v0.2.3](v0.2.3.en.md) | 2026-09-29 | A built-in tool that reads web pages as Markdown. | [FR](v0.2.3.fr.md) |
16+| [v0.2.2](v0.2.2.en.md) | 2026-09-28 | **No change to mm's code.** Sample configurations and Docker Sandbox kits v3. | [FR](v0.2.2.fr.md) |
17+| [v0.2.1](v0.2.1.en.md) | 2026-09-27 | Compression that works with thinking models. | [FR](v0.2.1.fr.md) |
18+| [v0.2.0](v0.2.0.en.md) | 2026-09-26 | A long prompt is no longer taken for a hang. | [FR](v0.2.0.fr.md) |
19+| [v0.1.9](v0.1.9.en.md) | 2026-09-26 | ## Fixed | [FR](v0.1.9.fr.md) |
20+| [v0.1.8](v0.1.8.en.md) | 2026-09-26 | Token usage in editors, and tool calls cut off by `max_tokens`. | [FR](v0.1.8.fr.md) |
21+| [v0.1.7](v0.1.7.en.md) | 2026-09-25 | You can choose the name an editor sees. | [FR](v0.1.7.fr.md) |
22+| [v0.1.6](v0.1.6.en.md) | 2026-09-23 | MCP servers now work through the sbx gateway. | [FR](v0.1.6.fr.md) |
23+| [v0.1.5](v0.1.5.en.md) | 2026-09-23 | ## Fixed | [FR](v0.1.5.fr.md) |
24+| [v0.1.4](v0.1.4.en.md) | 2026-09-23 | MCP servers in a Docker sandbox, and a new authentication type. | [FR](v0.1.4.fr.md) |
25+| [v0.1.3](v0.1.3.en.md) | 2026-09-23 | **No change to mm itself.** A one-line fix in the `demo/` example program: it now names its model as a plain string. | [FR](v0.1.3.fr.md) |
26+| [v0.1.2](v0.1.2.en.md) | 2026-09-23 | mini-me can use MCP servers. | [FR](v0.1.2.fr.md) |
27+| [v0.1.1](v0.1.1.en.md) | 2026-09-22 | A plan tool, and a one-shot prompt mode. | [FR](v0.1.1.fr.md) |
28+| [v0.1.0](v0.1.0.en.md) | 2026-09-21 | The Docker Sandbox images are now published by hand. **The `mm` binary is the same as in v0.0.4.** | [FR](v0.1.0.fr.md) |
29+| [v0.0.4](v0.0.4.en.md) | 2026-09-21 | A release-pipeline fix only: **the `mm` binary is the same as in v0.0.3.** | [FR](v0.0.4.fr.md) |
30+| [v0.0.3](v0.0.3.en.md) | 2026-09-21 | Thinking models, a version you can see, a window you can always leave, `edit_file` that tolerates whitespace, smarter skills, and Docker San | [FR](v0.0.3.fr.md) |
31+| [v0.0.2](v0.0.2.en.md) | 2026-09-18 | Language servers behind a new `lsp` tool, errors kept in the session file, and stronger guards against a model that loops. | [FR](v0.0.2.fr.md) |
32+| [v0.0.1](v0.0.1.en.md) | 2026-09-17 | The `bash` tool can be turned off. | [FR](v0.0.1.fr.md) |
33+| [v0.0.0](v0.0.0.en.md) | 2026-09-17 | The first release of mini-me (`mm`): a minimal coding agent for local models. Its loop has one main tool (`bash`) and is built to guard smal | [FR](v0.0.0.fr.md) |
new file mode 100644
@@ -0,0 +1,33 @@
1+# Release notes
2+
3+One note per tag, in English (`.en.md`) and French (`.fr.md`). Rebuilt on 2026-10-18 from the diffs between tags, the tickets and the project history: the commit messages say nothing.
4+
5+| Version | Date | Summary | |
6+|---|---|---|---|
7+| [v0.7.0](v0.7.0.en.md) | 2026-10-18 | Free a model's memory, see which hooks ran, pick up an earlier session from your editor, and screen questions with guard models. | [FR](v0.7.0.fr.md) |
8+| [v0.6.0](v0.6.0.en.md) | 2026-10-17 | Your own tools declared in the agent file, and answers saved to a file without being written again. | [FR](v0.6.0.fr.md) |
9+| [v0.5.0](v0.5.0.en.md) | 2026-10-16 | Hooks: your own scripts around generations, tool calls and calls to other agents. | [FR](v0.5.0.fr.md) |
10+| [v0.4.0](v0.4.0.en.md) | 2026-10-12 | The agent can ask you questions, MCP tools come with their parameters, and the demos move under `demos/`. | [FR](v0.4.0.fr.md) |
11+| [v0.3.0](v0.3.0.en.md) | 2026-10-09 | Yolo mode, Genkit 1.13.1, an `ask_agent` tool, traces of model traffic, JSON answers checked against Go types, and `AGENTS.md`. | [FR](v0.3.0.fr.md) |
12+| [v0.2.6](v0.2.6.en.md) | 2026-10-03 | An agent can ask another mm agent. | [FR](v0.2.6.fr.md) |
13+| [v0.2.5](v0.2.5.en.md) | 2026-10-01 | Two new model servers: llmman and Ollama. | [FR](v0.2.5.fr.md) |
14+| [v0.2.4](v0.2.4.en.md) | 2026-09-30 | The server's prompt cache works again from one question to the next. | [FR](v0.2.4.fr.md) |
15+| [v0.2.3](v0.2.3.en.md) | 2026-09-29 | A built-in tool that reads web pages as Markdown. | [FR](v0.2.3.fr.md) |
16+| [v0.2.2](v0.2.2.en.md) | 2026-09-28 | **No change to mm's code.** Sample configurations and Docker Sandbox kits v3. | [FR](v0.2.2.fr.md) |
17+| [v0.2.1](v0.2.1.en.md) | 2026-09-27 | Compression that works with thinking models. | [FR](v0.2.1.fr.md) |
18+| [v0.2.0](v0.2.0.en.md) | 2026-09-26 | A long prompt is no longer taken for a hang. | [FR](v0.2.0.fr.md) |
19+| [v0.1.9](v0.1.9.en.md) | 2026-09-26 | ## Fixed | [FR](v0.1.9.fr.md) |
20+| [v0.1.8](v0.1.8.en.md) | 2026-09-26 | Token usage in editors, and tool calls cut off by `max_tokens`. | [FR](v0.1.8.fr.md) |
21+| [v0.1.7](v0.1.7.en.md) | 2026-09-25 | You can choose the name an editor sees. | [FR](v0.1.7.fr.md) |
22+| [v0.1.6](v0.1.6.en.md) | 2026-09-23 | MCP servers now work through the sbx gateway. | [FR](v0.1.6.fr.md) |
23+| [v0.1.5](v0.1.5.en.md) | 2026-09-23 | ## Fixed | [FR](v0.1.5.fr.md) |
24+| [v0.1.4](v0.1.4.en.md) | 2026-09-23 | MCP servers in a Docker sandbox, and a new authentication type. | [FR](v0.1.4.fr.md) |
25+| [v0.1.3](v0.1.3.en.md) | 2026-09-23 | **No change to mm itself.** A one-line fix in the `demo/` example program: it now names its model as a plain string. | [FR](v0.1.3.fr.md) |
26+| [v0.1.2](v0.1.2.en.md) | 2026-09-23 | mini-me can use MCP servers. | [FR](v0.1.2.fr.md) |
27+| [v0.1.1](v0.1.1.en.md) | 2026-09-22 | A plan tool, and a one-shot prompt mode. | [FR](v0.1.1.fr.md) |
28+| [v0.1.0](v0.1.0.en.md) | 2026-09-21 | The Docker Sandbox images are now published by hand. **The `mm` binary is the same as in v0.0.4.** | [FR](v0.1.0.fr.md) |
29+| [v0.0.4](v0.0.4.en.md) | 2026-09-21 | A release-pipeline fix only: **the `mm` binary is the same as in v0.0.3.** | [FR](v0.0.4.fr.md) |
30+| [v0.0.3](v0.0.3.en.md) | 2026-09-21 | Thinking models, a version you can see, a window you can always leave, `edit_file` that tolerates whitespace, smarter skills, and Docker San | [FR](v0.0.3.fr.md) |
31+| [v0.0.2](v0.0.2.en.md) | 2026-09-18 | Language servers behind a new `lsp` tool, errors kept in the session file, and stronger guards against a model that loops. | [FR](v0.0.2.fr.md) |
32+| [v0.0.1](v0.0.1.en.md) | 2026-09-17 | The `bash` tool can be turned off. | [FR](v0.0.1.fr.md) |
33+| [v0.0.0](v0.0.0.en.md) | 2026-09-17 | The first release of mini-me (`mm`): a minimal coding agent for local models. Its loop has one main tool (`bash`) and is built to guard smal | [FR](v0.0.0.fr.md) |
added release-notes/v0.0.0.en.md +61 -0
new file mode 100644
@@ -0,0 +1,61 @@
1+# mini-me v0.0.0
2+
3+*[Version française](v0.0.0.fr.md)*
4+
5+The first release of mini-me (`mm`): a minimal coding agent for local models. Its loop has one main tool (`bash`) and is built to guard small models against hallucinations, silent failures and endless loops.
6+
7+## What is in it
8+
9+### Three front ends, one loop
10+
11+- **Line mode**: `mm` in a terminal.
12+- **Window mode**: `mm -tui`, or `tui: true` in the configuration. It is a single window on tcell:
13+ - the conversation on top and a 3-row input box;
14+ - Enter sends the message, Ctrl-J adds a new line, Esc cancels the turn;
15+ - a `/` command picker;
16+ - with `permissions: ask`, `bash`, `write_file` and `edit_file` wait for your answer.
17+- **Editor mode**: `mm -acp` speaks the Agent Client Protocol (Zed and other ACP editors). Commands are advertised in the editor's slash menu.
18+
19+### Model servers
20+
21+- **Providers**: Docker Model Runner (`dmr`) and llama.cpp (`llamacpp`), both through the OpenAI-compatible API.
22+- **Start-up checks**: mm checks that the server answers, finds the context window, and warns when the model is missing or has no chat template.
23+
24+### Tools and skills
25+
26+- **Tools**: `bash`, `read_file`, `write_file`, `edit_file`.
27+- **Skills** (`read_skill`): stored as `<name>.md` or `<name>/SKILL.md`. A relative `skillsDir` is resolved from the start directory, or next to the configuration file in editor mode. The banner warns, with the full path, when no skill is found.
28+- **`@path` in a question** attaches a file or a directory: `📎` in the terminal, `resource_link` in an editor.
29+
30+### Commands
31+
32+- `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume [id]`, `/help` and `/copy [n|all]`.
33+- In an editor: `/new` and `/compact`.
34+
35+### Sessions and context
36+
37+- **Persistent sessions, per project** (`sessions.enabled`, under `.mm/sessions`):
38+ - `/sessions` and `/resume` in the terminal;
39+ - `session/load` in an editor, which replays the conversation. Zed's "Reload Agent" uses it.
40+- **Context compression** (`context.*`): a summary plus the last raw turns, triggered automatically in the line mode or by hand with `/compact`.
41+
42+### Safeguards
43+
44+- A **loop detector** for repeated identical tool calls.
45+- A **watchdog** (`watchdogTimeout`) for a stream that has gone silent.
46+- `maxTurns` and `maxOutput`.
47+
48+### Window mode extras
49+
50+- **Native text selection** works: the mouse is free by default, and F2 toggles mouse capture.
51+- **Copying**: Ctrl-Y and `/copy` copy an answer, through `pbcopy`, `wl-copy`, `xclip` or `xsel` first, then OSC 52.
52+- **Long pastes** fold into a `[Pasted #1 · 50 lines · 1.0k chars]` token. The model still receives the full text.
53+
54+## Distribution
55+
56+- **Binaries** for macOS arm64, Linux amd64/arm64 and Windows amd64/arm64, built by the release workflow (`go build`, `CGO_ENABLED=0`), with `SHA256SUMS`.
57+- **`install.sh`** builds `mm` and copies it to `/usr/local/bin`.
58+
59+## Documentation
60+
61+- **EN + FR documentation**, organised the Diátaxis way: a getting-started tutorial, how-to guides, reference (CLI, configuration, tools, slash commands, ACP, skill format) and explanations (architecture, providers, compression, sessions).
new file mode 100644
@@ -0,0 +1,61 @@
1+# mini-me v0.0.0
2+
3+*[Version française](v0.0.0.fr.md)*
4+
5+The first release of mini-me (`mm`): a minimal coding agent for local models. Its loop has one main tool (`bash`) and is built to guard small models against hallucinations, silent failures and endless loops.
6+
7+## What is in it
8+
9+### Three front ends, one loop
10+
11+- **Line mode**: `mm` in a terminal.
12+- **Window mode**: `mm -tui`, or `tui: true` in the configuration. It is a single window on tcell:
13+ - the conversation on top and a 3-row input box;
14+ - Enter sends the message, Ctrl-J adds a new line, Esc cancels the turn;
15+ - a `/` command picker;
16+ - with `permissions: ask`, `bash`, `write_file` and `edit_file` wait for your answer.
17+- **Editor mode**: `mm -acp` speaks the Agent Client Protocol (Zed and other ACP editors). Commands are advertised in the editor's slash menu.
18+
19+### Model servers
20+
21+- **Providers**: Docker Model Runner (`dmr`) and llama.cpp (`llamacpp`), both through the OpenAI-compatible API.
22+- **Start-up checks**: mm checks that the server answers, finds the context window, and warns when the model is missing or has no chat template.
23+
24+### Tools and skills
25+
26+- **Tools**: `bash`, `read_file`, `write_file`, `edit_file`.
27+- **Skills** (`read_skill`): stored as `<name>.md` or `<name>/SKILL.md`. A relative `skillsDir` is resolved from the start directory, or next to the configuration file in editor mode. The banner warns, with the full path, when no skill is found.
28+- **`@path` in a question** attaches a file or a directory: `📎` in the terminal, `resource_link` in an editor.
29+
30+### Commands
31+
32+- `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume [id]`, `/help` and `/copy [n|all]`.
33+- In an editor: `/new` and `/compact`.
34+
35+### Sessions and context
36+
37+- **Persistent sessions, per project** (`sessions.enabled`, under `.mm/sessions`):
38+ - `/sessions` and `/resume` in the terminal;
39+ - `session/load` in an editor, which replays the conversation. Zed's "Reload Agent" uses it.
40+- **Context compression** (`context.*`): a summary plus the last raw turns, triggered automatically in the line mode or by hand with `/compact`.
41+
42+### Safeguards
43+
44+- A **loop detector** for repeated identical tool calls.
45+- A **watchdog** (`watchdogTimeout`) for a stream that has gone silent.
46+- `maxTurns` and `maxOutput`.
47+
48+### Window mode extras
49+
50+- **Native text selection** works: the mouse is free by default, and F2 toggles mouse capture.
51+- **Copying**: Ctrl-Y and `/copy` copy an answer, through `pbcopy`, `wl-copy`, `xclip` or `xsel` first, then OSC 52.
52+- **Long pastes** fold into a `[Pasted #1 · 50 lines · 1.0k chars]` token. The model still receives the full text.
53+
54+## Distribution
55+
56+- **Binaries** for macOS arm64, Linux amd64/arm64 and Windows amd64/arm64, built by the release workflow (`go build`, `CGO_ENABLED=0`), with `SHA256SUMS`.
57+- **`install.sh`** builds `mm` and copies it to `/usr/local/bin`.
58+
59+## Documentation
60+
61+- **EN + FR documentation**, organised the Diátaxis way: a getting-started tutorial, how-to guides, reference (CLI, configuration, tools, slash commands, ACP, skill format) and explanations (architecture, providers, compression, sessions).
added release-notes/v0.0.0.fr.md +61 -0
new file mode 100644
@@ -0,0 +1,61 @@
1+# mini-me v0.0.0
2+
3+*[English version](v0.0.0.en.md)*
4+
5+La première version de mini-me (`mm`) : un agent de code minimal pour modèles locaux. Sa boucle a un outil principal (`bash`) et protège les petits modèles contre les hallucinations, les échecs silencieux et les boucles sans fin.
6+
7+## Contenu
8+
9+### Trois interfaces, une seule boucle
10+
11+- **Mode ligne** : `mm` dans un terminal.
12+- **Mode fenêtre** : `mm -tui`, ou `tui: true` dans la configuration. C'est une fenêtre unique sur tcell :
13+ - la conversation en haut et une zone de saisie de 3 lignes ;
14+ - Entrée envoie le message, Ctrl-J ajoute une nouvelle ligne, Échap annule le tour ;
15+ - un sélecteur de commandes `/` ;
16+ - avec `permissions: ask`, `bash`, `write_file` et `edit_file` attendent votre réponse.
17+- **Mode éditeur** : `mm -acp` parle l'Agent Client Protocol (Zed et les autres éditeurs ACP). Les commandes apparaissent dans le menu slash de l'éditeur.
18+
19+### Serveurs de modèles
20+
21+- **Fournisseurs** : Docker Model Runner (`dmr`) et llama.cpp (`llamacpp`), tous deux via l'API compatible OpenAI.
22+- **Vérifications au démarrage** : mm vérifie que le serveur répond, trouve la fenêtre de contexte, et prévient si le modèle est absent ou n'a pas de chat template.
23+
24+### Outils et skills
25+
26+- **Outils** : `bash`, `read_file`, `write_file`, `edit_file`.
27+- **Skills** (`read_skill`) : rangés en `<nom>.md` ou en `<nom>/SKILL.md`. Un `skillsDir` relatif est résolu depuis le répertoire de lancement, ou à côté du fichier de configuration en mode éditeur. La bannière prévient, avec le chemin complet, quand aucun skill n'est trouvé.
28+- **`@chemin` dans une question** joint un fichier ou un répertoire : `📎` dans le terminal, `resource_link` dans un éditeur.
29+
30+### Commandes
31+
32+- `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume [id]`, `/help` et `/copy [n|all]`.
33+- Dans un éditeur : `/new` et `/compact`.
34+
35+### Sessions et contexte
36+
37+- **Sessions persistantes, par projet** (`sessions.enabled`, dans `.mm/sessions`) :
38+ - `/sessions` et `/resume` dans le terminal ;
39+ - `session/load` dans un éditeur, qui rejoue la conversation. Le « Reload Agent » de Zed s'en sert.
40+- **Compression du contexte** (`context.*`) : un résumé plus les derniers tours bruts, déclenchée automatiquement en mode ligne ou à la main avec `/compact`.
41+
42+### Garde-fous
43+
44+- Un **détecteur de boucles** pour les appels d'outil identiques répétés.
45+- Un **chien de garde** (`watchdogTimeout`) pour un flux devenu silencieux.
46+- `maxTurns` et `maxOutput`.
47+
48+### En plus dans le mode fenêtre
49+
50+- **La sélection native du texte** fonctionne : la souris est libre par défaut, et F2 active ou coupe sa capture.
51+- **Copie** : Ctrl-Y et `/copy` copient une réponse, avec `pbcopy`, `wl-copy`, `xclip` ou `xsel` d'abord, puis OSC 52.
52+- **Les longs collages** se replient en un jeton `[Pasted #1 · 50 lines · 1.0k chars]`. Le modèle reçoit quand même le texte complet.
53+
54+## Distribution
55+
56+- **Binaires** pour macOS arm64, Linux amd64/arm64 et Windows amd64/arm64, construits par le workflow de release (`go build`, `CGO_ENABLED=0`), avec `SHA256SUMS`.
57+- **`install.sh`** construit `mm` et le copie dans `/usr/local/bin`.
58+
59+## Documentation
60+
61+- **Documentation EN + FR**, organisée selon Diátaxis : un tutoriel de démarrage, des guides pratiques, une référence (CLI, configuration, outils, commandes slash, ACP, format des skills) et des explications (architecture, fournisseurs, compression, sessions).
new file mode 100644
@@ -0,0 +1,61 @@
1+# mini-me v0.0.0
2+
3+*[English version](v0.0.0.en.md)*
4+
5+La première version de mini-me (`mm`) : un agent de code minimal pour modèles locaux. Sa boucle a un outil principal (`bash`) et protège les petits modèles contre les hallucinations, les échecs silencieux et les boucles sans fin.
6+
7+## Contenu
8+
9+### Trois interfaces, une seule boucle
10+
11+- **Mode ligne** : `mm` dans un terminal.
12+- **Mode fenêtre** : `mm -tui`, ou `tui: true` dans la configuration. C'est une fenêtre unique sur tcell :
13+ - la conversation en haut et une zone de saisie de 3 lignes ;
14+ - Entrée envoie le message, Ctrl-J ajoute une nouvelle ligne, Échap annule le tour ;
15+ - un sélecteur de commandes `/` ;
16+ - avec `permissions: ask`, `bash`, `write_file` et `edit_file` attendent votre réponse.
17+- **Mode éditeur** : `mm -acp` parle l'Agent Client Protocol (Zed et les autres éditeurs ACP). Les commandes apparaissent dans le menu slash de l'éditeur.
18+
19+### Serveurs de modèles
20+
21+- **Fournisseurs** : Docker Model Runner (`dmr`) et llama.cpp (`llamacpp`), tous deux via l'API compatible OpenAI.
22+- **Vérifications au démarrage** : mm vérifie que le serveur répond, trouve la fenêtre de contexte, et prévient si le modèle est absent ou n'a pas de chat template.
23+
24+### Outils et skills
25+
26+- **Outils** : `bash`, `read_file`, `write_file`, `edit_file`.
27+- **Skills** (`read_skill`) : rangés en `<nom>.md` ou en `<nom>/SKILL.md`. Un `skillsDir` relatif est résolu depuis le répertoire de lancement, ou à côté du fichier de configuration en mode éditeur. La bannière prévient, avec le chemin complet, quand aucun skill n'est trouvé.
28+- **`@chemin` dans une question** joint un fichier ou un répertoire : `📎` dans le terminal, `resource_link` dans un éditeur.
29+
30+### Commandes
31+
32+- `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume [id]`, `/help` et `/copy [n|all]`.
33+- Dans un éditeur : `/new` et `/compact`.
34+
35+### Sessions et contexte
36+
37+- **Sessions persistantes, par projet** (`sessions.enabled`, dans `.mm/sessions`) :
38+ - `/sessions` et `/resume` dans le terminal ;
39+ - `session/load` dans un éditeur, qui rejoue la conversation. Le « Reload Agent » de Zed s'en sert.
40+- **Compression du contexte** (`context.*`) : un résumé plus les derniers tours bruts, déclenchée automatiquement en mode ligne ou à la main avec `/compact`.
41+
42+### Garde-fous
43+
44+- Un **détecteur de boucles** pour les appels d'outil identiques répétés.
45+- Un **chien de garde** (`watchdogTimeout`) pour un flux devenu silencieux.
46+- `maxTurns` et `maxOutput`.
47+
48+### En plus dans le mode fenêtre
49+
50+- **La sélection native du texte** fonctionne : la souris est libre par défaut, et F2 active ou coupe sa capture.
51+- **Copie** : Ctrl-Y et `/copy` copient une réponse, avec `pbcopy`, `wl-copy`, `xclip` ou `xsel` d'abord, puis OSC 52.
52+- **Les longs collages** se replient en un jeton `[Pasted #1 · 50 lines · 1.0k chars]`. Le modèle reçoit quand même le texte complet.
53+
54+## Distribution
55+
56+- **Binaires** pour macOS arm64, Linux amd64/arm64 et Windows amd64/arm64, construits par le workflow de release (`go build`, `CGO_ENABLED=0`), avec `SHA256SUMS`.
57+- **`install.sh`** construit `mm` et le copie dans `/usr/local/bin`.
58+
59+## Documentation
60+
61+- **Documentation EN + FR**, organisée selon Diátaxis : un tutoriel de démarrage, des guides pratiques, une référence (CLI, configuration, outils, commandes slash, ACP, format des skills) et des explications (architecture, fournisseurs, compression, sessions).
added release-notes/v0.0.1.en.md +16 -0
new file mode 100644
@@ -0,0 +1,16 @@
1+# mini-me v0.0.1
2+
3+*[Version française](v0.0.1.fr.md)*
4+
5+The `bash` tool can be turned off.
6+
7+## New
8+
9+- **`bashTool`** (default `true`). With `bashTool: false`, the model can still read and edit files and load skills, but it never runs a command.
10+ - The banner shows `tools: none` and warns when no tool is declared at all.
11+ - If your system prompt still announces a bash tool, reword it.
12+
13+## Changed
14+
15+- **`README.md`** rewritten: what mini-me is, how to install it, the three modes, Zed and the other ACP editors.
16+- **Comments in `agent.llamacpp.yaml`** simplified to match `agent.dmr.yaml`. `--jinja` is no longer presented as required.
new file mode 100644
@@ -0,0 +1,16 @@
1+# mini-me v0.0.1
2+
3+*[Version française](v0.0.1.fr.md)*
4+
5+The `bash` tool can be turned off.
6+
7+## New
8+
9+- **`bashTool`** (default `true`). With `bashTool: false`, the model can still read and edit files and load skills, but it never runs a command.
10+ - The banner shows `tools: none` and warns when no tool is declared at all.
11+ - If your system prompt still announces a bash tool, reword it.
12+
13+## Changed
14+
15+- **`README.md`** rewritten: what mini-me is, how to install it, the three modes, Zed and the other ACP editors.
16+- **Comments in `agent.llamacpp.yaml`** simplified to match `agent.dmr.yaml`. `--jinja` is no longer presented as required.
added release-notes/v0.0.1.fr.md +16 -0
new file mode 100644
@@ -0,0 +1,16 @@
1+# mini-me v0.0.1
2+
3+*[English version](v0.0.1.en.md)*
4+
5+On peut désactiver l'outil `bash`.
6+
7+## Nouveautés
8+
9+- **`bashTool`** (`true` par défaut). Avec `bashTool: false`, le modèle peut toujours lire et modifier des fichiers et charger des skills, mais il n'exécute jamais de commande.
10+ - La bannière affiche `tools: none` et prévient quand aucun outil n'est déclaré.
11+ - Si votre prompt système annonce encore un outil bash, reformulez-le.
12+
13+## Changements
14+
15+- **`README.md`** réécrit : ce qu'est mini-me, comment l'installer, les trois modes, Zed et les autres éditeurs ACP.
16+- **Commentaires d'`agent.llamacpp.yaml`** simplifiés comme ceux d'`agent.dmr.yaml`. `--jinja` n'est plus présenté comme obligatoire.
new file mode 100644
@@ -0,0 +1,16 @@
1+# mini-me v0.0.1
2+
3+*[English version](v0.0.1.en.md)*
4+
5+On peut désactiver l'outil `bash`.
6+
7+## Nouveautés
8+
9+- **`bashTool`** (`true` par défaut). Avec `bashTool: false`, le modèle peut toujours lire et modifier des fichiers et charger des skills, mais il n'exécute jamais de commande.
10+ - La bannière affiche `tools: none` et prévient quand aucun outil n'est déclaré.
11+ - Si votre prompt système annonce encore un outil bash, reformulez-le.
12+
13+## Changements
14+
15+- **`README.md`** réécrit : ce qu'est mini-me, comment l'installer, les trois modes, Zed et les autres éditeurs ACP.
16+- **Commentaires d'`agent.llamacpp.yaml`** simplifiés comme ceux d'`agent.dmr.yaml`. `--jinja` n'est plus présenté comme obligatoire.
added release-notes/v0.0.2.en.md +37 -0
new file mode 100644
@@ -0,0 +1,37 @@
1+# mini-me v0.0.2
2+
3+*[Version française](v0.0.2.fr.md)*
4+
5+Language servers behind a new `lsp` tool, errors kept in the session file, and stronger guards against a model that loops.
6+
7+## New
8+
9+### LSP support (#17)
10+
11+- **New `lsp` tool** with six actions: `definition`, `references`, `hover`, `symbols`, `diagnostics` and `rename`.
12+ - The model names the `symbol`; columns are optional.
13+ - `rename` asks for permission, like file edits.
14+- **Configuration**: list your servers under `lsp.servers` (`name`, `command`, `args`, `extensions`). The file extension picks the server. A server missing from the `PATH` is skipped with a warning.
15+- **`agent.lsp.yaml`**: a ready-made configuration.
16+- **`skills/lsp-navigation`**: a skill for code navigation.
17+- **New how-to guide**: Navigate code with LSP (EN + FR). It covers seven servers.
18+
19+### Errors recorded in the session (#21)
20+
21+- **New `errors` array** in the session file: failed generations, failed compactions and failed `/resume`. The model never reads it. A turn you cancel is not counted as an error.
22+- **`/sessions` and `/resume`** show `⚠️ N error(s)`.
23+
24+## Fixed
25+
26+- **Window mode**: the input box now wraps on words (#20). ↑ and ↓ move between rows.
27+- **Loops**:
28+ - after 3 more identical calls once the loop is flagged, the turn stops with an explained error, also kept in the session;
29+ - a tool call whose arguments run away (repeating patterns, or more than 256 KB) is cut while it streams, and the spinner shows `Composing a tool call`.
30+- **`edit_file`**: when `old` appears several times, the refusal now gives the line numbers of the occurrences.
31+- **`lsp rename`**:
32+ - without `new_name`, the error says which call to send next;
33+ - a mangled `symbol` is cleaned up, and `"Greet Hello"` is read as a rename from `Greet` to `Hello`.
34+
35+## Upgrading
36+
37+- **Go 1.26 or later** is now needed to build mm (required by `go.lsp.dev`).
new file mode 100644
@@ -0,0 +1,37 @@
1+# mini-me v0.0.2
2+
3+*[Version française](v0.0.2.fr.md)*
4+
5+Language servers behind a new `lsp` tool, errors kept in the session file, and stronger guards against a model that loops.
6+
7+## New
8+
9+### LSP support (#17)
10+
11+- **New `lsp` tool** with six actions: `definition`, `references`, `hover`, `symbols`, `diagnostics` and `rename`.
12+ - The model names the `symbol`; columns are optional.
13+ - `rename` asks for permission, like file edits.
14+- **Configuration**: list your servers under `lsp.servers` (`name`, `command`, `args`, `extensions`). The file extension picks the server. A server missing from the `PATH` is skipped with a warning.
15+- **`agent.lsp.yaml`**: a ready-made configuration.
16+- **`skills/lsp-navigation`**: a skill for code navigation.
17+- **New how-to guide**: Navigate code with LSP (EN + FR). It covers seven servers.
18+
19+### Errors recorded in the session (#21)
20+
21+- **New `errors` array** in the session file: failed generations, failed compactions and failed `/resume`. The model never reads it. A turn you cancel is not counted as an error.
22+- **`/sessions` and `/resume`** show `⚠️ N error(s)`.
23+
24+## Fixed
25+
26+- **Window mode**: the input box now wraps on words (#20). ↑ and ↓ move between rows.
27+- **Loops**:
28+ - after 3 more identical calls once the loop is flagged, the turn stops with an explained error, also kept in the session;
29+ - a tool call whose arguments run away (repeating patterns, or more than 256 KB) is cut while it streams, and the spinner shows `Composing a tool call`.
30+- **`edit_file`**: when `old` appears several times, the refusal now gives the line numbers of the occurrences.
31+- **`lsp rename`**:
32+ - without `new_name`, the error says which call to send next;
33+ - a mangled `symbol` is cleaned up, and `"Greet Hello"` is read as a rename from `Greet` to `Hello`.
34+
35+## Upgrading
36+
37+- **Go 1.26 or later** is now needed to build mm (required by `go.lsp.dev`).
added release-notes/v0.0.2.fr.md +37 -0
new file mode 100644
@@ -0,0 +1,37 @@
1+# mini-me v0.0.2
2+
3+*[English version](v0.0.2.en.md)*
4+
5+Des serveurs de langage derrière un nouvel outil `lsp`, les erreurs gardées dans le fichier de session, et des garde-fous plus solides contre un modèle qui boucle.
6+
7+## Nouveautés
8+
9+### Support LSP (#17)
10+
11+- **Nouvel outil `lsp`** avec six actions : `definition`, `references`, `hover`, `symbols`, `diagnostics` et `rename`.
12+ - Le modèle nomme le `symbol` ; les colonnes sont facultatives.
13+ - `rename` demande la permission, comme les modifications de fichiers.
14+- **Configuration** : listez vos serveurs sous `lsp.servers` (`name`, `command`, `args`, `extensions`). L'extension du fichier choisit le serveur. Un serveur absent du `PATH` est ignoré, avec un avertissement.
15+- **`agent.lsp.yaml`** : une configuration prête à l'emploi.
16+- **`skills/lsp-navigation`** : un skill pour naviguer dans le code.
17+- **Nouveau guide pratique** : Naviguer dans le code avec LSP (EN + FR). Il couvre sept serveurs.
18+
19+### Erreurs enregistrées dans la session (#21)
20+
21+- **Nouveau tableau `errors`** dans le fichier de session : générations échouées, compressions échouées et `/resume` échoués. Le modèle ne le lit jamais. Un tour que vous annulez ne compte pas comme une erreur.
22+- **`/sessions` et `/resume`** affichent `⚠️ N error(s)`.
23+
24+## Corrigé
25+
26+- **Mode fenêtre** : la zone de saisie revient maintenant à la ligne sur les mots (#20). ↑ et ↓ passent d'une ligne à l'autre.
27+- **Boucles** :
28+ - après 3 appels identiques de plus une fois la boucle signalée, le tour s'arrête avec une erreur expliquée, gardée aussi dans la session ;
29+ - un appel d'outil dont les arguments s'emballent (motifs qui se répètent, ou plus de 256 Ko) est coupé pendant qu'il arrive, et le spinner affiche `Composing a tool call`.
30+- **`edit_file`** : quand `old` apparaît plusieurs fois, le refus donne maintenant les numéros de ligne des occurrences.
31+- **`lsp rename`** :
32+ - sans `new_name`, l'erreur dit quel appel envoyer ensuite ;
33+ - un `symbol` abîmé est nettoyé, et `"Greet Hello"` est lu comme un renommage de `Greet` en `Hello`.
34+
35+## Mise à jour
36+
37+- **Go 1.26 ou plus récent** est maintenant nécessaire pour construire mm (exigé par `go.lsp.dev`).
new file mode 100644
@@ -0,0 +1,37 @@
1+# mini-me v0.0.2
2+
3+*[English version](v0.0.2.en.md)*
4+
5+Des serveurs de langage derrière un nouvel outil `lsp`, les erreurs gardées dans le fichier de session, et des garde-fous plus solides contre un modèle qui boucle.
6+
7+## Nouveautés
8+
9+### Support LSP (#17)
10+
11+- **Nouvel outil `lsp`** avec six actions : `definition`, `references`, `hover`, `symbols`, `diagnostics` et `rename`.
12+ - Le modèle nomme le `symbol` ; les colonnes sont facultatives.
13+ - `rename` demande la permission, comme les modifications de fichiers.
14+- **Configuration** : listez vos serveurs sous `lsp.servers` (`name`, `command`, `args`, `extensions`). L'extension du fichier choisit le serveur. Un serveur absent du `PATH` est ignoré, avec un avertissement.
15+- **`agent.lsp.yaml`** : une configuration prête à l'emploi.
16+- **`skills/lsp-navigation`** : un skill pour naviguer dans le code.
17+- **Nouveau guide pratique** : Naviguer dans le code avec LSP (EN + FR). Il couvre sept serveurs.
18+
19+### Erreurs enregistrées dans la session (#21)
20+
21+- **Nouveau tableau `errors`** dans le fichier de session : générations échouées, compressions échouées et `/resume` échoués. Le modèle ne le lit jamais. Un tour que vous annulez ne compte pas comme une erreur.
22+- **`/sessions` et `/resume`** affichent `⚠️ N error(s)`.
23+
24+## Corrigé
25+
26+- **Mode fenêtre** : la zone de saisie revient maintenant à la ligne sur les mots (#20). ↑ et ↓ passent d'une ligne à l'autre.
27+- **Boucles** :
28+ - après 3 appels identiques de plus une fois la boucle signalée, le tour s'arrête avec une erreur expliquée, gardée aussi dans la session ;
29+ - un appel d'outil dont les arguments s'emballent (motifs qui se répètent, ou plus de 256 Ko) est coupé pendant qu'il arrive, et le spinner affiche `Composing a tool call`.
30+- **`edit_file`** : quand `old` apparaît plusieurs fois, le refus donne maintenant les numéros de ligne des occurrences.
31+- **`lsp rename`** :
32+ - sans `new_name`, l'erreur dit quel appel envoyer ensuite ;
33+ - un `symbol` abîmé est nettoyé, et `"Greet Hello"` est lu comme un renommage de `Greet` en `Hello`.
34+
35+## Mise à jour
36+
37+- **Go 1.26 ou plus récent** est maintenant nécessaire pour construire mm (exigé par `go.lsp.dev`).
added release-notes/v0.0.3.en.md +51 -0
new file mode 100644
@@ -0,0 +1,51 @@
1+# mini-me v0.0.3
2+
3+*[Version française](v0.0.3.fr.md)*
4+
5+Thinking models, a version you can see, a window you can always leave, `edit_file` that tolerates whitespace, smarter skills, and Docker Sandboxes.
6+
7+## New
8+
9+### Thinking models (#29)
10+
11+- **The reasoning is shown**: dim and in italics in the terminal, as a separate block in the window, and as `agent_thought_chunk` in an editor. `showThinking: false` hides it.
12+- **The watchdog no longer kills a model that only reasons.** Before this fix, a turn that reasoned for longer than `watchdogTimeout` was killed as "stalled", because reasoning-only chunks were not counted as activity.
13+
14+### Version and about (#2, #22, #23)
15+
16+- **`mm --version`** prints the version, and **`mm --about`** prints one screen about mm.
17+- **The version is visible**: at the start of the banner (`mm v0.0.3 | config: …`) and in the window's status bar.
18+
19+### Window mode
20+
21+- **Pickers** (#37, #38):
22+ - an `@` file picker, usable anywhere in a sentence: Tab walks into a directory;
23+ - a `$` skill picker: `$name` stays in front, followed by the skill's parameters.
24+- **Shorter model id in the header** (#24): `huggingface.co/unsloth/qwen3.5-2b-gguf:Q4_K_M` becomes `qwen3.5-2b:Q4_K_M`.
25+
26+### Skills (#15)
27+
28+- **Lookup by name**: skills are looked up by the name in their catalogue, not by their file path.
29+- **Keywords**: the `keywords:` line is now read and sent with the catalogue (8 keywords at most).
30+- **New `bg-jobs` skill**: the shipped prompts already mentioned it, but it did not exist.
31+- **Banner warnings** for a skill with no description, a skill name used twice, and keywords cut off.
32+- **`📖` count**: now shown even at zero, and it counts only the skills actually loaded.
33+- **In an editor**: skills appear in the slash menu as `/<skill> [parameters]`.
34+- **A routing evaluation** (`internal/eval`, enabled with `MM_EVAL`) measures which skill the model picks for each request.
35+
36+### Docker Sandboxes
37+
38+- A **template and two kits** (`mm-tui`, `mm-acp`) under `docker-sbx/`.
39+- **New how-to guide**: Run in a Docker sandbox.
40+
41+## Fixed
42+
43+- **A stuck turn can always be left in the window** (#26):
44+ - `/quit` and `/abort` act at once instead of waiting in the queue;
45+ - a second Ctrl-C quits.
46+- **`bash` and background jobs**: a command that starts a background job no longer blocks the tool for ever. The job keeps running.
47+- **`edit_file` tolerates whitespace** (#27): tabs against spaces, and spaces at the start or end of the text.
48+ - The match must still be unique.
49+ - The new text is re-indented the way the file is.
50+ - The headline says when whitespace was ignored.
51+ - A failed match quotes the closest lines of the file.
new file mode 100644
@@ -0,0 +1,51 @@
1+# mini-me v0.0.3
2+
3+*[Version française](v0.0.3.fr.md)*
4+
5+Thinking models, a version you can see, a window you can always leave, `edit_file` that tolerates whitespace, smarter skills, and Docker Sandboxes.
6+
7+## New
8+
9+### Thinking models (#29)
10+
11+- **The reasoning is shown**: dim and in italics in the terminal, as a separate block in the window, and as `agent_thought_chunk` in an editor. `showThinking: false` hides it.
12+- **The watchdog no longer kills a model that only reasons.** Before this fix, a turn that reasoned for longer than `watchdogTimeout` was killed as "stalled", because reasoning-only chunks were not counted as activity.
13+
14+### Version and about (#2, #22, #23)
15+
16+- **`mm --version`** prints the version, and **`mm --about`** prints one screen about mm.
17+- **The version is visible**: at the start of the banner (`mm v0.0.3 | config: …`) and in the window's status bar.
18+
19+### Window mode
20+
21+- **Pickers** (#37, #38):
22+ - an `@` file picker, usable anywhere in a sentence: Tab walks into a directory;
23+ - a `$` skill picker: `$name` stays in front, followed by the skill's parameters.
24+- **Shorter model id in the header** (#24): `huggingface.co/unsloth/qwen3.5-2b-gguf:Q4_K_M` becomes `qwen3.5-2b:Q4_K_M`.
25+
26+### Skills (#15)
27+
28+- **Lookup by name**: skills are looked up by the name in their catalogue, not by their file path.
29+- **Keywords**: the `keywords:` line is now read and sent with the catalogue (8 keywords at most).
30+- **New `bg-jobs` skill**: the shipped prompts already mentioned it, but it did not exist.
31+- **Banner warnings** for a skill with no description, a skill name used twice, and keywords cut off.
32+- **`📖` count**: now shown even at zero, and it counts only the skills actually loaded.
33+- **In an editor**: skills appear in the slash menu as `/<skill> [parameters]`.
34+- **A routing evaluation** (`internal/eval`, enabled with `MM_EVAL`) measures which skill the model picks for each request.
35+
36+### Docker Sandboxes
37+
38+- A **template and two kits** (`mm-tui`, `mm-acp`) under `docker-sbx/`.
39+- **New how-to guide**: Run in a Docker sandbox.
40+
41+## Fixed
42+
43+- **A stuck turn can always be left in the window** (#26):
44+ - `/quit` and `/abort` act at once instead of waiting in the queue;
45+ - a second Ctrl-C quits.
46+- **`bash` and background jobs**: a command that starts a background job no longer blocks the tool for ever. The job keeps running.
47+- **`edit_file` tolerates whitespace** (#27): tabs against spaces, and spaces at the start or end of the text.
48+ - The match must still be unique.
49+ - The new text is re-indented the way the file is.
50+ - The headline says when whitespace was ignored.
51+ - A failed match quotes the closest lines of the file.
added release-notes/v0.0.3.fr.md +51 -0
new file mode 100644
@@ -0,0 +1,51 @@
1+# mini-me v0.0.3
2+
3+*[English version](v0.0.3.en.md)*
4+
5+Les modèles qui réfléchissent, une version visible, une fenêtre qu'on peut toujours quitter, un `edit_file` qui tolère les espaces, des skills plus malins, et les Docker Sandboxes.
6+
7+## Nouveautés
8+
9+### Modèles qui réfléchissent (#29)
10+
11+- **Le raisonnement est affiché** : grisé et en italique dans le terminal, dans un bloc à part dans la fenêtre, et en `agent_thought_chunk` dans un éditeur. `showThinking: false` le masque.
12+- **Le chien de garde ne tue plus un modèle qui ne fait que raisonner.** Avant ce correctif, un tour qui raisonnait plus longtemps que `watchdogTimeout` était tué comme « bloqué », parce que les fragments de raisonnement ne comptaient pas comme une activité.
13+
14+### Version et « à propos » (#2, #22, #23)
15+
16+- **`mm --version`** affiche la version, et **`mm --about`** affiche un écran de présentation de mm.
17+- **La version est visible** : au début de la bannière (`mm v0.0.3 | config: …`) et dans la barre d'état de la fenêtre.
18+
19+### Mode fenêtre
20+
21+- **Sélecteurs** (#37, #38) :
22+ - un sélecteur de fichiers `@`, utilisable n'importe où dans une phrase : Tab entre dans un répertoire ;
23+ - un sélecteur de skills `$` : `$nom` reste en tête, suivi des paramètres du skill.
24+- **Identifiant du modèle raccourci dans l'en-tête** (#24) : `huggingface.co/unsloth/qwen3.5-2b-gguf:Q4_K_M` devient `qwen3.5-2b:Q4_K_M`.
25+
26+### Skills (#15)
27+
28+- **Recherche par nom** : les skills sont cherchés par le nom de leur catalogue, plus par leur chemin de fichier.
29+- **Mots-clés** : la ligne `keywords:` est maintenant lue et envoyée avec le catalogue (8 mots-clés au plus).
30+- **Nouveau skill `bg-jobs`** : les prompts livrés le mentionnaient déjà, mais il n'existait pas.
31+- **Avertissements dans la bannière** pour un skill sans description, un nom de skill utilisé deux fois et des mots-clés tronqués.
32+- **Compteur `📖`** : affiché maintenant même à zéro, et il ne compte que les skills réellement chargés.
33+- **Dans un éditeur** : les skills apparaissent dans le menu slash sous la forme `/<skill> [paramètres]`.
34+- **Une évaluation du routage** (`internal/eval`, activée par `MM_EVAL`) mesure quel skill le modèle choisit pour chaque demande.
35+
36+### Docker Sandboxes
37+
38+- Un **template et deux kits** (`mm-tui`, `mm-acp`) dans `docker-sbx/`.
39+- **Nouveau guide pratique** : Lancer dans un Docker sandbox.
40+
41+## Corrigé
42+
43+- **On peut toujours quitter un tour bloqué dans la fenêtre** (#26) :
44+ - `/quit` et `/abort` agissent tout de suite au lieu d'attendre dans la file ;
45+ - un second Ctrl-C quitte.
46+- **`bash` et les tâches de fond** : une commande qui lance une tâche de fond ne bloque plus l'outil pour toujours. La tâche continue de tourner.
47+- **`edit_file` tolère les espaces** (#27) : tabulations contre espaces, et espaces au début ou à la fin du texte.
48+ - La correspondance doit toujours être unique.
49+ - Le nouveau texte est réindenté comme le fichier.
50+ - Le titre du résultat dit quand des espaces ont été ignorés.
51+ - Une recherche ratée cite les lignes les plus proches du fichier.
new file mode 100644
@@ -0,0 +1,51 @@
1+# mini-me v0.0.3
2+
3+*[English version](v0.0.3.en.md)*
4+
5+Les modèles qui réfléchissent, une version visible, une fenêtre qu'on peut toujours quitter, un `edit_file` qui tolère les espaces, des skills plus malins, et les Docker Sandboxes.
6+
7+## Nouveautés
8+
9+### Modèles qui réfléchissent (#29)
10+
11+- **Le raisonnement est affiché** : grisé et en italique dans le terminal, dans un bloc à part dans la fenêtre, et en `agent_thought_chunk` dans un éditeur. `showThinking: false` le masque.
12+- **Le chien de garde ne tue plus un modèle qui ne fait que raisonner.** Avant ce correctif, un tour qui raisonnait plus longtemps que `watchdogTimeout` était tué comme « bloqué », parce que les fragments de raisonnement ne comptaient pas comme une activité.
13+
14+### Version et « à propos » (#2, #22, #23)
15+
16+- **`mm --version`** affiche la version, et **`mm --about`** affiche un écran de présentation de mm.
17+- **La version est visible** : au début de la bannière (`mm v0.0.3 | config: …`) et dans la barre d'état de la fenêtre.
18+
19+### Mode fenêtre
20+
21+- **Sélecteurs** (#37, #38) :
22+ - un sélecteur de fichiers `@`, utilisable n'importe où dans une phrase : Tab entre dans un répertoire ;
23+ - un sélecteur de skills `$` : `$nom` reste en tête, suivi des paramètres du skill.
24+- **Identifiant du modèle raccourci dans l'en-tête** (#24) : `huggingface.co/unsloth/qwen3.5-2b-gguf:Q4_K_M` devient `qwen3.5-2b:Q4_K_M`.
25+
26+### Skills (#15)
27+
28+- **Recherche par nom** : les skills sont cherchés par le nom de leur catalogue, plus par leur chemin de fichier.
29+- **Mots-clés** : la ligne `keywords:` est maintenant lue et envoyée avec le catalogue (8 mots-clés au plus).
30+- **Nouveau skill `bg-jobs`** : les prompts livrés le mentionnaient déjà, mais il n'existait pas.
31+- **Avertissements dans la bannière** pour un skill sans description, un nom de skill utilisé deux fois et des mots-clés tronqués.
32+- **Compteur `📖`** : affiché maintenant même à zéro, et il ne compte que les skills réellement chargés.
33+- **Dans un éditeur** : les skills apparaissent dans le menu slash sous la forme `/<skill> [paramètres]`.
34+- **Une évaluation du routage** (`internal/eval`, activée par `MM_EVAL`) mesure quel skill le modèle choisit pour chaque demande.
35+
36+### Docker Sandboxes
37+
38+- Un **template et deux kits** (`mm-tui`, `mm-acp`) dans `docker-sbx/`.
39+- **Nouveau guide pratique** : Lancer dans un Docker sandbox.
40+
41+## Corrigé
42+
43+- **On peut toujours quitter un tour bloqué dans la fenêtre** (#26) :
44+ - `/quit` et `/abort` agissent tout de suite au lieu d'attendre dans la file ;
45+ - un second Ctrl-C quitte.
46+- **`bash` et les tâches de fond** : une commande qui lance une tâche de fond ne bloque plus l'outil pour toujours. La tâche continue de tourner.
47+- **`edit_file` tolère les espaces** (#27) : tabulations contre espaces, et espaces au début ou à la fin du texte.
48+ - La correspondance doit toujours être unique.
49+ - Le nouveau texte est réindenté comme le fichier.
50+ - Le titre du résultat dit quand des espaces ont été ignorés.
51+ - Une recherche ratée cite les lignes les plus proches du fichier.
added release-notes/v0.0.4.en.md +9 -0
new file mode 100644
@@ -0,0 +1,9 @@
1+# mini-me v0.0.4
2+
3+*[Version française](v0.0.4.fr.md)*
4+
5+A release-pipeline fix only: **the `mm` binary is the same as in v0.0.3.**
6+
7+## Changed
8+
9+- **CI, Docker Hub job**: the job that publishes the sandbox template and kits now cleans up the credentials before using them (stray newlines and spaces). It refuses an e-mail address used as the Docker handle, and prints diagnostics about the path to Docker Hub.
new file mode 100644
@@ -0,0 +1,9 @@
1+# mini-me v0.0.4
2+
3+*[Version française](v0.0.4.fr.md)*
4+
5+A release-pipeline fix only: **the `mm` binary is the same as in v0.0.3.**
6+
7+## Changed
8+
9+- **CI, Docker Hub job**: the job that publishes the sandbox template and kits now cleans up the credentials before using them (stray newlines and spaces). It refuses an e-mail address used as the Docker handle, and prints diagnostics about the path to Docker Hub.
added release-notes/v0.0.4.fr.md +9 -0
new file mode 100644
@@ -0,0 +1,9 @@
1+# mini-me v0.0.4
2+
3+*[English version](v0.0.4.en.md)*
4+
5+Seulement une correction de la chaîne de release : **le binaire `mm` est le même qu'en v0.0.3.**
6+
7+## Changements
8+
9+- **CI, tâche Docker Hub** : la tâche qui publie le template et les kits du sandbox nettoie maintenant les identifiants avant de s'en servir (retours à la ligne et espaces en trop). Elle refuse une adresse e-mail utilisée comme identifiant Docker, et affiche un diagnostic du chemin vers Docker Hub.
new file mode 100644
@@ -0,0 +1,9 @@
1+# mini-me v0.0.4
2+
3+*[English version](v0.0.4.en.md)*
4+
5+Seulement une correction de la chaîne de release : **le binaire `mm` est le même qu'en v0.0.3.**
6+
7+## Changements
8+
9+- **CI, tâche Docker Hub** : la tâche qui publie le template et les kits du sandbox nettoie maintenant les identifiants avant de s'en servir (retours à la ligne et espaces en trop). Elle refuse une adresse e-mail utilisée comme identifiant Docker, et affiche un diagnostic du chemin vers Docker Hub.
added release-notes/v0.1.0.en.md +13 -0
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.1.0
2+
3+*[Version française](v0.1.0.fr.md)*
4+
5+The Docker Sandbox images are now published by hand. **The `mm` binary is the same as in v0.0.4.**
6+
7+## Changed
8+
9+- **The Docker Hub job is removed from the release workflow** (it failed at `docker login` on the CI runner).
10+- **New script `docker-sbx/publish.sh`** builds the multi-arch template and pushes the two kits (#35).
11+ - It takes the version from `release.env` or from its argument.
12+ - It needs `DOCKER_HANDLE` and `DOCKER_PAT`.
13+- **The sandbox how-to guide (EN + FR)** explains how to publish a version.
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.1.0
2+
3+*[Version française](v0.1.0.fr.md)*
4+
5+The Docker Sandbox images are now published by hand. **The `mm` binary is the same as in v0.0.4.**
6+
7+## Changed
8+
9+- **The Docker Hub job is removed from the release workflow** (it failed at `docker login` on the CI runner).
10+- **New script `docker-sbx/publish.sh`** builds the multi-arch template and pushes the two kits (#35).
11+ - It takes the version from `release.env` or from its argument.
12+ - It needs `DOCKER_HANDLE` and `DOCKER_PAT`.
13+- **The sandbox how-to guide (EN + FR)** explains how to publish a version.
added release-notes/v0.1.0.fr.md +13 -0
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.1.0
2+
3+*[English version](v0.1.0.en.md)*
4+
5+Les images Docker Sandbox se publient maintenant à la main. **Le binaire `mm` est le même qu'en v0.0.4.**
6+
7+## Changements
8+
9+- **La tâche Docker Hub est retirée du workflow de release** (elle échouait au `docker login` sur le runner de la CI).
10+- **Nouveau script `docker-sbx/publish.sh`** : il construit le template multi-architecture et pousse les deux kits (#35).
11+ - Il prend la version dans `release.env` ou en argument.
12+ - Il a besoin de `DOCKER_HANDLE` et `DOCKER_PAT`.
13+- **Le guide pratique du sandbox (EN + FR)** explique comment publier une version.
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.1.0
2+
3+*[English version](v0.1.0.en.md)*
4+
5+Les images Docker Sandbox se publient maintenant à la main. **Le binaire `mm` est le même qu'en v0.0.4.**
6+
7+## Changements
8+
9+- **La tâche Docker Hub est retirée du workflow de release** (elle échouait au `docker login` sur le runner de la CI).
10+- **Nouveau script `docker-sbx/publish.sh`** : il construit le template multi-architecture et pousse les deux kits (#35).
11+ - Il prend la version dans `release.env` ou en argument.
12+ - Il a besoin de `DOCKER_HANDLE` et `DOCKER_PAT`.
13+- **Le guide pratique du sandbox (EN + FR)** explique comment publier une version.
added release-notes/v0.1.1.en.md +19 -0
new file mode 100644
@@ -0,0 +1,19 @@
1+# mini-me v0.1.1
2+
3+*[Version française](v0.1.1.fr.md)*
4+
5+A plan tool, and a one-shot prompt mode.
6+
7+## New
8+
9+- **`plan` tool** (#25): the agent creates and follows a list of tasks for a multi-step request. After each answer, a `📋 [2/3] …` line shows the progress.
10+ - It is turned on with `planTool` (default `true`).
11+ - A `plan-manager` skill ships with it.
12+- **`-p` / `-prompt`** (#32): runs one prompt and exits. There is no banner and no saved session.
13+ - The configuration file can come before or after the flag: `mm -p "…" agent.yaml`.
14+- **New how-to guides** (EN + FR): Manage plans and tasks, Run a single prompt.
15+
16+## Docs
17+
18+- **`INSTALL.MACOS.md`**: installing on macOS.
19+- **Sandboxes**: guides for using the sandbox from Zed and from the Turbo editors.
new file mode 100644
@@ -0,0 +1,19 @@
1+# mini-me v0.1.1
2+
3+*[Version française](v0.1.1.fr.md)*
4+
5+A plan tool, and a one-shot prompt mode.
6+
7+## New
8+
9+- **`plan` tool** (#25): the agent creates and follows a list of tasks for a multi-step request. After each answer, a `📋 [2/3] …` line shows the progress.
10+ - It is turned on with `planTool` (default `true`).
11+ - A `plan-manager` skill ships with it.
12+- **`-p` / `-prompt`** (#32): runs one prompt and exits. There is no banner and no saved session.
13+ - The configuration file can come before or after the flag: `mm -p "…" agent.yaml`.
14+- **New how-to guides** (EN + FR): Manage plans and tasks, Run a single prompt.
15+
16+## Docs
17+
18+- **`INSTALL.MACOS.md`**: installing on macOS.
19+- **Sandboxes**: guides for using the sandbox from Zed and from the Turbo editors.
added release-notes/v0.1.1.fr.md +19 -0
new file mode 100644
@@ -0,0 +1,19 @@
1+# mini-me v0.1.1
2+
3+*[English version](v0.1.1.en.md)*
4+
5+Un outil de plan, et un mode prompt unique.
6+
7+## Nouveautés
8+
9+- **Outil `plan`** (#25) : l'agent crée et suit une liste de tâches pour une demande en plusieurs étapes. Après chaque réponse, une ligne `📋 [2/3] …` montre l'avancement.
10+ - Il s'active avec `planTool` (`true` par défaut).
11+ - Un skill `plan-manager` est livré avec.
12+- **`-p` / `-prompt`** (#32) : lance un seul prompt et quitte. Il n'y a pas de bannière ni de session enregistrée.
13+ - Le fichier de configuration peut venir avant ou après l'option : `mm -p "…" agent.yaml`.
14+- **Nouveaux guides pratiques** (EN + FR) : Gérer des plans et des tâches, Lancer un seul prompt.
15+
16+## Documentation
17+
18+- **`INSTALL.MACOS.md`** : installer sur macOS.
19+- **Sandboxes** : des guides pour utiliser le sandbox depuis Zed et depuis les éditeurs Turbo.
new file mode 100644
@@ -0,0 +1,19 @@
1+# mini-me v0.1.1
2+
3+*[English version](v0.1.1.en.md)*
4+
5+Un outil de plan, et un mode prompt unique.
6+
7+## Nouveautés
8+
9+- **Outil `plan`** (#25) : l'agent crée et suit une liste de tâches pour une demande en plusieurs étapes. Après chaque réponse, une ligne `📋 [2/3] …` montre l'avancement.
10+ - Il s'active avec `planTool` (`true` par défaut).
11+ - Un skill `plan-manager` est livré avec.
12+- **`-p` / `-prompt`** (#32) : lance un seul prompt et quitte. Il n'y a pas de bannière ni de session enregistrée.
13+ - Le fichier de configuration peut venir avant ou après l'option : `mm -p "…" agent.yaml`.
14+- **Nouveaux guides pratiques** (EN + FR) : Gérer des plans et des tâches, Lancer un seul prompt.
15+
16+## Documentation
17+
18+- **`INSTALL.MACOS.md`** : installer sur macOS.
19+- **Sandboxes** : des guides pour utiliser le sandbox depuis Zed et depuis les éditeurs Turbo.
added release-notes/v0.1.2.en.md +16 -0
new file mode 100644
@@ -0,0 +1,16 @@
1+# mini-me v0.1.2
2+
3+*[Version française](v0.1.2.fr.md)*
4+
5+mini-me can use MCP servers.
6+
7+## New
8+
9+- **MCP client** (#4), configured under `mcp:`:
10+ - **Transport**: HTTP. Stdio is not implemented yet.
11+ - **Authentication**: `none`, `bearer` (token from an environment variable) or `api_key` (custom header).
12+ - **Per server**: `headers`, `timeout` and a `tools` filter.
13+ - **Permissions**: `mcp.permissions` defaults to `ask`, and can be set per server.
14+ - **Start-up**: tools are discovered within `discoveryTimeout`.
15+- **`agent.mcp-example.yaml`**: an example configuration.
16+- **MCP documentation**: a tutorial, how-to guides (Context7, GitHub, Wikipedia), reference (configuration, authentication, available tools) and explanations (architecture, design decisions).
new file mode 100644
@@ -0,0 +1,16 @@
1+# mini-me v0.1.2
2+
3+*[Version française](v0.1.2.fr.md)*
4+
5+mini-me can use MCP servers.
6+
7+## New
8+
9+- **MCP client** (#4), configured under `mcp:`:
10+ - **Transport**: HTTP. Stdio is not implemented yet.
11+ - **Authentication**: `none`, `bearer` (token from an environment variable) or `api_key` (custom header).
12+ - **Per server**: `headers`, `timeout` and a `tools` filter.
13+ - **Permissions**: `mcp.permissions` defaults to `ask`, and can be set per server.
14+ - **Start-up**: tools are discovered within `discoveryTimeout`.
15+- **`agent.mcp-example.yaml`**: an example configuration.
16+- **MCP documentation**: a tutorial, how-to guides (Context7, GitHub, Wikipedia), reference (configuration, authentication, available tools) and explanations (architecture, design decisions).
added release-notes/v0.1.2.fr.md +16 -0
new file mode 100644
@@ -0,0 +1,16 @@
1+# mini-me v0.1.2
2+
3+*[English version](v0.1.2.en.md)*
4+
5+mini-me peut utiliser des serveurs MCP.
6+
7+## Nouveautés
8+
9+- **Client MCP** (#4), configuré sous `mcp:` :
10+ - **Transport** : HTTP. Le stdio n'est pas encore implémenté.
11+ - **Authentification** : `none`, `bearer` (jeton lu dans une variable d'environnement) ou `api_key` (en-tête personnalisé).
12+ - **Par serveur** : `headers`, `timeout` et un filtre `tools`.
13+ - **Permissions** : `mcp.permissions` vaut `ask` par défaut, et se règle par serveur.
14+ - **Au démarrage** : les outils sont découverts dans la limite de `discoveryTimeout`.
15+- **`agent.mcp-example.yaml`** : un exemple de configuration.
16+- **Documentation MCP** : un tutoriel, des guides pratiques (Context7, GitHub, Wikipedia), une référence (configuration, authentification, outils disponibles) et des explications (architecture, choix de conception).
new file mode 100644
@@ -0,0 +1,16 @@
1+# mini-me v0.1.2
2+
3+*[English version](v0.1.2.en.md)*
4+
5+mini-me peut utiliser des serveurs MCP.
6+
7+## Nouveautés
8+
9+- **Client MCP** (#4), configuré sous `mcp:` :
10+ - **Transport** : HTTP. Le stdio n'est pas encore implémenté.
11+ - **Authentification** : `none`, `bearer` (jeton lu dans une variable d'environnement) ou `api_key` (en-tête personnalisé).
12+ - **Par serveur** : `headers`, `timeout` et un filtre `tools`.
13+ - **Permissions** : `mcp.permissions` vaut `ask` par défaut, et se règle par serveur.
14+ - **Au démarrage** : les outils sont découverts dans la limite de `discoveryTimeout`.
15+- **`agent.mcp-example.yaml`** : un exemple de configuration.
16+- **Documentation MCP** : un tutoriel, des guides pratiques (Context7, GitHub, Wikipedia), une référence (configuration, authentification, outils disponibles) et des explications (architecture, choix de conception).
added release-notes/v0.1.3.en.md +5 -0
new file mode 100644
@@ -0,0 +1,5 @@
1+# mini-me v0.1.3
2+
3+*[Version française](v0.1.3.fr.md)*
4+
5+**No change to mm itself.** A one-line fix in the `demo/` example program: it now names its model as a plain string.
new file mode 100644
@@ -0,0 +1,5 @@
1+# mini-me v0.1.3
2+
3+*[Version française](v0.1.3.fr.md)*
4+
5+**No change to mm itself.** A one-line fix in the `demo/` example program: it now names its model as a plain string.
added release-notes/v0.1.3.fr.md +5 -0
new file mode 100644
@@ -0,0 +1,5 @@
1+# mini-me v0.1.3
2+
3+*[English version](v0.1.3.en.md)*
4+
5+**Aucun changement dans mm lui-même.** Une correction d'une ligne dans le programme d'exemple de `demo/` : il nomme maintenant son modèle par une simple chaîne.
new file mode 100644
@@ -0,0 +1,5 @@
1+# mini-me v0.1.3
2+
3+*[English version](v0.1.3.en.md)*
4+
5+**Aucun changement dans mm lui-même.** Une correction d'une ligne dans le programme d'exemple de `demo/` : il nomme maintenant son modèle par une simple chaîne.
added release-notes/v0.1.4.en.md +11 -0
new file mode 100644
@@ -0,0 +1,11 @@
1+# mini-me v0.1.4
2+
3+*[Version française](v0.1.4.fr.md)*
4+
5+MCP servers in a Docker sandbox, and a new authentication type.
6+
7+## New
8+
9+- **The sbx MCP gateway is picked up automatically**: when `MCP_GATEWAY_URL` is set, mm adds it as an MCP server named `sbx-gateway`, next to the configured servers.
10+- **MCP authentication type `oauth2`**: the token is read from an environment variable (already obtained). New keys: `oauthClientId`, `oauthClientSecretEnv` and `oauthScopes`.
11+- **GitHub MCP demo configurations**: `demo/agent.dmr.github-oauth.yaml` and `demo/agent.dmr.sbx.yaml`, with `demo/MCP-GITHUB.md`.
new file mode 100644
@@ -0,0 +1,11 @@
1+# mini-me v0.1.4
2+
3+*[Version française](v0.1.4.fr.md)*
4+
5+MCP servers in a Docker sandbox, and a new authentication type.
6+
7+## New
8+
9+- **The sbx MCP gateway is picked up automatically**: when `MCP_GATEWAY_URL` is set, mm adds it as an MCP server named `sbx-gateway`, next to the configured servers.
10+- **MCP authentication type `oauth2`**: the token is read from an environment variable (already obtained). New keys: `oauthClientId`, `oauthClientSecretEnv` and `oauthScopes`.
11+- **GitHub MCP demo configurations**: `demo/agent.dmr.github-oauth.yaml` and `demo/agent.dmr.sbx.yaml`, with `demo/MCP-GITHUB.md`.
added release-notes/v0.1.4.fr.md +11 -0
new file mode 100644
@@ -0,0 +1,11 @@
1+# mini-me v0.1.4
2+
3+*[English version](v0.1.4.en.md)*
4+
5+Des serveurs MCP dans un Docker sandbox, et un nouveau type d'authentification.
6+
7+## Nouveautés
8+
9+- **La passerelle MCP de sbx est reprise automatiquement** : quand `MCP_GATEWAY_URL` est définie, mm l'ajoute comme serveur MCP nommé `sbx-gateway`, à côté des serveurs configurés.
10+- **Type d'authentification MCP `oauth2`** : le jeton est lu dans une variable d'environnement (déjà obtenu). Nouvelles clés : `oauthClientId`, `oauthClientSecretEnv` et `oauthScopes`.
11+- **Configurations de démo pour le MCP GitHub** : `demo/agent.dmr.github-oauth.yaml` et `demo/agent.dmr.sbx.yaml`, avec `demo/MCP-GITHUB.md`.
new file mode 100644
@@ -0,0 +1,11 @@
1+# mini-me v0.1.4
2+
3+*[English version](v0.1.4.en.md)*
4+
5+Des serveurs MCP dans un Docker sandbox, et un nouveau type d'authentification.
6+
7+## Nouveautés
8+
9+- **La passerelle MCP de sbx est reprise automatiquement** : quand `MCP_GATEWAY_URL` est définie, mm l'ajoute comme serveur MCP nommé `sbx-gateway`, à côté des serveurs configurés.
10+- **Type d'authentification MCP `oauth2`** : le jeton est lu dans une variable d'environnement (déjà obtenu). Nouvelles clés : `oauthClientId`, `oauthClientSecretEnv` et `oauthScopes`.
11+- **Configurations de démo pour le MCP GitHub** : `demo/agent.dmr.github-oauth.yaml` et `demo/agent.dmr.sbx.yaml`, avec `demo/MCP-GITHUB.md`.
added release-notes/v0.1.5.en.md +7 -0
new file mode 100644
@@ -0,0 +1,7 @@
1+# mini-me v0.1.5
2+
3+*[Version française](v0.1.5.fr.md)*
4+
5+## Fixed
6+
7+- **sbx MCP gateway**: mm now reads the token from `MCP_SENTINEL_TOKEN_NAME`, the variable sbx actually injects, instead of `MCP_GATEWAY_TOKEN`.
new file mode 100644
@@ -0,0 +1,7 @@
1+# mini-me v0.1.5
2+
3+*[Version française](v0.1.5.fr.md)*
4+
5+## Fixed
6+
7+- **sbx MCP gateway**: mm now reads the token from `MCP_SENTINEL_TOKEN_NAME`, the variable sbx actually injects, instead of `MCP_GATEWAY_TOKEN`.
added release-notes/v0.1.5.fr.md +7 -0
new file mode 100644
@@ -0,0 +1,7 @@
1+# mini-me v0.1.5
2+
3+*[English version](v0.1.5.en.md)*
4+
5+## Corrigé
6+
7+- **Passerelle MCP de sbx** : mm lit maintenant le jeton dans `MCP_SENTINEL_TOKEN_NAME`, la variable que sbx injecte vraiment, au lieu de `MCP_GATEWAY_TOKEN`.
new file mode 100644
@@ -0,0 +1,7 @@
1+# mini-me v0.1.5
2+
3+*[English version](v0.1.5.en.md)*
4+
5+## Corrigé
6+
7+- **Passerelle MCP de sbx** : mm lit maintenant le jeton dans `MCP_SENTINEL_TOKEN_NAME`, la variable que sbx injecte vraiment, au lieu de `MCP_GATEWAY_TOKEN`.
added release-notes/v0.1.6.en.md +14 -0
new file mode 100644
@@ -0,0 +1,14 @@
1+# mini-me v0.1.6
2+
3+*[Version française](v0.1.6.fr.md)*
4+
5+MCP servers now work through the sbx gateway.
6+
7+## Fixed
8+
9+- **The MCP HTTP client opens a real MCP session.** It sends `initialize` first and reuses `Mcp-Session-Id`. When the session is lost (404), it re-initialises once. It closes the session with `DELETE`, and reports HTTP errors with their status.
10+ - Before this fix, a stateful server such as the sbx gateway answered every call with `method "tools/list" is invalid during session initialization`.
11+
12+## Docs
13+
14+- **New section** in the sandbox how-to guide (EN + FR): "MCP servers through the sbx gateway".
new file mode 100644
@@ -0,0 +1,14 @@
1+# mini-me v0.1.6
2+
3+*[Version française](v0.1.6.fr.md)*
4+
5+MCP servers now work through the sbx gateway.
6+
7+## Fixed
8+
9+- **The MCP HTTP client opens a real MCP session.** It sends `initialize` first and reuses `Mcp-Session-Id`. When the session is lost (404), it re-initialises once. It closes the session with `DELETE`, and reports HTTP errors with their status.
10+ - Before this fix, a stateful server such as the sbx gateway answered every call with `method "tools/list" is invalid during session initialization`.
11+
12+## Docs
13+
14+- **New section** in the sandbox how-to guide (EN + FR): "MCP servers through the sbx gateway".
added release-notes/v0.1.6.fr.md +14 -0
new file mode 100644
@@ -0,0 +1,14 @@
1+# mini-me v0.1.6
2+
3+*[English version](v0.1.6.en.md)*
4+
5+Les serveurs MCP fonctionnent maintenant à travers la passerelle de sbx.
6+
7+## Corrigé
8+
9+- **Le client MCP HTTP ouvre une vraie session MCP.** Il envoie d'abord `initialize` et réutilise `Mcp-Session-Id`. Quand la session est perdue (404), il se réinitialise une fois. Il ferme la session avec `DELETE`, et remonte les erreurs HTTP avec leur statut.
10+ - Avant ce correctif, un serveur à état comme la passerelle de sbx répondait à chaque appel `method "tools/list" is invalid during session initialization`.
11+
12+## Documentation
13+
14+- **Nouvelle section** dans le guide pratique du sandbox (EN + FR) : « Serveurs MCP via la passerelle sbx ».
new file mode 100644
@@ -0,0 +1,14 @@
1+# mini-me v0.1.6
2+
3+*[English version](v0.1.6.en.md)*
4+
5+Les serveurs MCP fonctionnent maintenant à travers la passerelle de sbx.
6+
7+## Corrigé
8+
9+- **Le client MCP HTTP ouvre une vraie session MCP.** Il envoie d'abord `initialize` et réutilise `Mcp-Session-Id`. Quand la session est perdue (404), il se réinitialise une fois. Il ferme la session avec `DELETE`, et remonte les erreurs HTTP avec leur statut.
10+ - Avant ce correctif, un serveur à état comme la passerelle de sbx répondait à chaque appel `method "tools/list" is invalid during session initialization`.
11+
12+## Documentation
13+
14+- **Nouvelle section** dans le guide pratique du sandbox (EN + FR) : « Serveurs MCP via la passerelle sbx ».
added release-notes/v0.1.7.en.md +18 -0
new file mode 100644
@@ -0,0 +1,18 @@
1+# mini-me v0.1.7
2+
3+*[Version française](v0.1.7.fr.md)*
4+
5+You can choose the name an editor sees.
6+
7+## New
8+
9+- **`acp:` block** with `name` and `title`: how `mm -acp` introduces itself to the editor. The defaults are the former values (`bob`, `Bob (bash-first agent)`).
10+
11+## Changed
12+
13+- **Over ACP, mm now reports its own version** (`mm --version`), instead of the fixed `0.11.0`. An editor's log and a bug report now name the same build.
14+
15+## Docs
16+
17+- **Editor how-to guide** (EN + FR): changing the name, and a one-liner that shows what `initialize` answers.
18+- **The GitHub-MCP-in-sbx procedure**: `demo/MCP-GITHUB-SBX.md` (FR) and `.en.md`, plus the script `docker-sbx/mcp/github-mcp.sh`. The script runs the official GitHub server, read-only by default, and takes the token from the environment, the macOS keychain or `gh`.
new file mode 100644
@@ -0,0 +1,18 @@
1+# mini-me v0.1.7
2+
3+*[Version française](v0.1.7.fr.md)*
4+
5+You can choose the name an editor sees.
6+
7+## New
8+
9+- **`acp:` block** with `name` and `title`: how `mm -acp` introduces itself to the editor. The defaults are the former values (`bob`, `Bob (bash-first agent)`).
10+
11+## Changed
12+
13+- **Over ACP, mm now reports its own version** (`mm --version`), instead of the fixed `0.11.0`. An editor's log and a bug report now name the same build.
14+
15+## Docs
16+
17+- **Editor how-to guide** (EN + FR): changing the name, and a one-liner that shows what `initialize` answers.
18+- **The GitHub-MCP-in-sbx procedure**: `demo/MCP-GITHUB-SBX.md` (FR) and `.en.md`, plus the script `docker-sbx/mcp/github-mcp.sh`. The script runs the official GitHub server, read-only by default, and takes the token from the environment, the macOS keychain or `gh`.
added release-notes/v0.1.7.fr.md +18 -0
new file mode 100644
@@ -0,0 +1,18 @@
1+# mini-me v0.1.7
2+
3+*[English version](v0.1.7.en.md)*
4+
5+On peut choisir le nom que voit un éditeur.
6+
7+## Nouveautés
8+
9+- **Bloc `acp:`** avec `name` et `title` : la façon dont `mm -acp` se présente à l'éditeur. Les valeurs par défaut sont les anciennes (`bob`, `Bob (bash-first agent)`).
10+
11+## Changements
12+
13+- **En ACP, mm annonce maintenant sa propre version** (`mm --version`), au lieu du `0.11.0` figé. Le journal de l'éditeur et un rapport de bug nomment maintenant le même binaire.
14+
15+## Documentation
16+
17+- **Guide pratique de l'éditeur** (EN + FR) : changer le nom, et une commande d'une ligne qui montre ce que répond `initialize`.
18+- **La procédure MCP GitHub dans sbx** : `demo/MCP-GITHUB-SBX.md` (FR) et `.en.md`, plus le script `docker-sbx/mcp/github-mcp.sh`. Le script lance le serveur GitHub officiel, en lecture seule par défaut, et prend le jeton dans l'environnement, le trousseau macOS ou `gh`.
new file mode 100644
@@ -0,0 +1,18 @@
1+# mini-me v0.1.7
2+
3+*[English version](v0.1.7.en.md)*
4+
5+On peut choisir le nom que voit un éditeur.
6+
7+## Nouveautés
8+
9+- **Bloc `acp:`** avec `name` et `title` : la façon dont `mm -acp` se présente à l'éditeur. Les valeurs par défaut sont les anciennes (`bob`, `Bob (bash-first agent)`).
10+
11+## Changements
12+
13+- **En ACP, mm annonce maintenant sa propre version** (`mm --version`), au lieu du `0.11.0` figé. Le journal de l'éditeur et un rapport de bug nomment maintenant le même binaire.
14+
15+## Documentation
16+
17+- **Guide pratique de l'éditeur** (EN + FR) : changer le nom, et une commande d'une ligne qui montre ce que répond `initialize`.
18+- **La procédure MCP GitHub dans sbx** : `demo/MCP-GITHUB-SBX.md` (FR) et `.en.md`, plus le script `docker-sbx/mcp/github-mcp.sh`. Le script lance le serveur GitHub officiel, en lecture seule par défaut, et prend le jeton dans l'environnement, le trousseau macOS ou `gh`.
added release-notes/v0.1.8.en.md +14 -0
new file mode 100644
@@ -0,0 +1,14 @@
1+# mini-me v0.1.8
2+
3+*[Version française](v0.1.8.fr.md)*
4+
5+Token usage in editors, and tool calls cut off by `max_tokens`.
6+
7+## New
8+
9+- **ACP `usage_update`**: after each model call, mm sends the tokens used and the context window. Each prompt response carries `usage` totals.
10+ - In editor mode, mm asks the server for the context window again before each turn, as long as it is unknown.
11+
12+## Fixed
13+
14+- **Truncated tool-call arguments.** When `max_tokens` cuts the arguments of a call (a long `write_file`, for example), the turn no longer ends on a parse error. The call is handed back to the model twice with a note, then the turn ends with a one-line explanation.
new file mode 100644
@@ -0,0 +1,14 @@
1+# mini-me v0.1.8
2+
3+*[Version française](v0.1.8.fr.md)*
4+
5+Token usage in editors, and tool calls cut off by `max_tokens`.
6+
7+## New
8+
9+- **ACP `usage_update`**: after each model call, mm sends the tokens used and the context window. Each prompt response carries `usage` totals.
10+ - In editor mode, mm asks the server for the context window again before each turn, as long as it is unknown.
11+
12+## Fixed
13+
14+- **Truncated tool-call arguments.** When `max_tokens` cuts the arguments of a call (a long `write_file`, for example), the turn no longer ends on a parse error. The call is handed back to the model twice with a note, then the turn ends with a one-line explanation.
added release-notes/v0.1.8.fr.md +14 -0
new file mode 100644
@@ -0,0 +1,14 @@
1+# mini-me v0.1.8
2+
3+*[English version](v0.1.8.en.md)*
4+
5+La consommation de tokens dans les éditeurs, et les appels d'outil coupés par `max_tokens`.
6+
7+## Nouveautés
8+
9+- **`usage_update` en ACP** : après chaque appel au modèle, mm envoie les tokens utilisés et la fenêtre de contexte. Chaque réponse à un prompt porte les totaux `usage`.
10+ - En mode éditeur, mm redemande la fenêtre de contexte au serveur avant chaque tour, tant qu'elle est inconnue.
11+
12+## Corrigé
13+
14+- **Arguments d'appel d'outil tronqués.** Quand `max_tokens` coupe les arguments d'un appel (un long `write_file`, par exemple), le tour ne se termine plus sur une erreur d'analyse. L'appel est rendu deux fois au modèle avec une note, puis le tour s'arrête avec une explication d'une ligne.
new file mode 100644
@@ -0,0 +1,14 @@
1+# mini-me v0.1.8
2+
3+*[English version](v0.1.8.en.md)*
4+
5+La consommation de tokens dans les éditeurs, et les appels d'outil coupés par `max_tokens`.
6+
7+## Nouveautés
8+
9+- **`usage_update` en ACP** : après chaque appel au modèle, mm envoie les tokens utilisés et la fenêtre de contexte. Chaque réponse à un prompt porte les totaux `usage`.
10+ - En mode éditeur, mm redemande la fenêtre de contexte au serveur avant chaque tour, tant qu'elle est inconnue.
11+
12+## Corrigé
13+
14+- **Arguments d'appel d'outil tronqués.** Quand `max_tokens` coupe les arguments d'un appel (un long `write_file`, par exemple), le tour ne se termine plus sur une erreur d'analyse. L'appel est rendu deux fois au modèle avec une note, puis le tour s'arrête avec une explication d'une ligne.
added release-notes/v0.1.9.en.md +7 -0
new file mode 100644
@@ -0,0 +1,7 @@
1+# mini-me v0.1.9
2+
3+*[Version française](v0.1.9.fr.md)*
4+
5+## Fixed
6+
7+- **A stream that breaks partway through.** When llama.cpp sends an SSE `error:` event, which openai-go turned into `unexpected end of JSON input`, mm keeps the server's message and retries the model call once. If it fails again, the explanation quotes the server; a cut connection is reported as such.
new file mode 100644
@@ -0,0 +1,7 @@
1+# mini-me v0.1.9
2+
3+*[Version française](v0.1.9.fr.md)*
4+
5+## Fixed
6+
7+- **A stream that breaks partway through.** When llama.cpp sends an SSE `error:` event, which openai-go turned into `unexpected end of JSON input`, mm keeps the server's message and retries the model call once. If it fails again, the explanation quotes the server; a cut connection is reported as such.
added release-notes/v0.1.9.fr.md +7 -0
new file mode 100644
@@ -0,0 +1,7 @@
1+# mini-me v0.1.9
2+
3+*[English version](v0.1.9.en.md)*
4+
5+## Corrigé
6+
7+- **Un flux qui se coupe en cours de route.** Quand llama.cpp envoie un événement SSE `error:`, qu'openai-go transformait en `unexpected end of JSON input`, mm garde le message du serveur et refait l'appel au modèle une fois. S'il échoue encore, l'explication cite le serveur ; une connexion coupée est signalée comme telle.
new file mode 100644
@@ -0,0 +1,7 @@
1+# mini-me v0.1.9
2+
3+*[English version](v0.1.9.en.md)*
4+
5+## Corrigé
6+
7+- **Un flux qui se coupe en cours de route.** Quand llama.cpp envoie un événement SSE `error:`, qu'openai-go transformait en `unexpected end of JSON input`, mm garde le message du serveur et refait l'appel au modèle une fois. S'il échoue encore, l'explication cite le serveur ; une connexion coupée est signalée comme telle.
added release-notes/v0.2.0.en.md +18 -0
new file mode 100644
@@ -0,0 +1,18 @@
1+# mini-me v0.2.0
2+
3+*[Version française](v0.2.0.fr.md)*
4+
5+A long prompt is no longer taken for a hang.
6+
7+## New
8+
9+- **`firstTokenTimeout`** (default `3m`): the silence tolerated before a model call's first token, while the server reads the prompt. It is never shorter than `watchdogTimeout`.
10+- **Prompt progress keeps the turn alive**: on llama.cpp-style servers, mm asks for `return_progress`, and each `prompt_progress` counts as a sign of life.
11+
12+## Fixed
13+
14+- **Long prompts**: a turn with a long prompt (about 25k tokens) was killed as "stalled" by the 30 s watchdog while the server was still reading it.
15+
16+## Upgrading
17+
18+- **The old advice to raise `watchdogTimeout` for long prompts is gone** from the shipped YAML files: `firstTokenTimeout` now covers that wait.
new file mode 100644
@@ -0,0 +1,18 @@
1+# mini-me v0.2.0
2+
3+*[Version française](v0.2.0.fr.md)*
4+
5+A long prompt is no longer taken for a hang.
6+
7+## New
8+
9+- **`firstTokenTimeout`** (default `3m`): the silence tolerated before a model call's first token, while the server reads the prompt. It is never shorter than `watchdogTimeout`.
10+- **Prompt progress keeps the turn alive**: on llama.cpp-style servers, mm asks for `return_progress`, and each `prompt_progress` counts as a sign of life.
11+
12+## Fixed
13+
14+- **Long prompts**: a turn with a long prompt (about 25k tokens) was killed as "stalled" by the 30 s watchdog while the server was still reading it.
15+
16+## Upgrading
17+
18+- **The old advice to raise `watchdogTimeout` for long prompts is gone** from the shipped YAML files: `firstTokenTimeout` now covers that wait.
added release-notes/v0.2.0.fr.md +18 -0
new file mode 100644
@@ -0,0 +1,18 @@
1+# mini-me v0.2.0
2+
3+*[English version](v0.2.0.en.md)*
4+
5+Un long prompt n'est plus pris pour un blocage.
6+
7+## Nouveautés
8+
9+- **`firstTokenTimeout`** (`3m` par défaut) : le silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt. Il n'est jamais plus court que `watchdogTimeout`.
10+- **La progression du prompt garde le tour en vie** : sur les serveurs de type llama.cpp, mm demande `return_progress`, et chaque `prompt_progress` compte comme un signe de vie.
11+
12+## Corrigé
13+
14+- **Longs prompts** : un tour avec un long prompt (environ 25k tokens) était tué comme « bloqué » par le chien de garde de 30 s pendant que le serveur le lisait encore.
15+
16+## Mise à jour
17+
18+- **L'ancien conseil d'augmenter `watchdogTimeout` pour les longs prompts disparaît** des fichiers YAML livrés : `firstTokenTimeout` couvre maintenant cette attente.
new file mode 100644
@@ -0,0 +1,18 @@
1+# mini-me v0.2.0
2+
3+*[English version](v0.2.0.en.md)*
4+
5+Un long prompt n'est plus pris pour un blocage.
6+
7+## Nouveautés
8+
9+- **`firstTokenTimeout`** (`3m` par défaut) : le silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt. Il n'est jamais plus court que `watchdogTimeout`.
10+- **La progression du prompt garde le tour en vie** : sur les serveurs de type llama.cpp, mm demande `return_progress`, et chaque `prompt_progress` compte comme un signe de vie.
11+
12+## Corrigé
13+
14+- **Longs prompts** : un tour avec un long prompt (environ 25k tokens) était tué comme « bloqué » par le chien de garde de 30 s pendant que le serveur le lisait encore.
15+
16+## Mise à jour
17+
18+- **L'ancien conseil d'augmenter `watchdogTimeout` pour les longs prompts disparaît** des fichiers YAML livrés : `firstTokenTimeout` couvre maintenant cette attente.
added release-notes/v0.2.1.en.md +13 -0
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.2.1
2+
3+*[Version française](v0.2.1.fr.md)*
4+
5+Compression that works with thinking models.
6+
7+## Fixed
8+
9+- **Empty compression summaries with a thinking model**: the reasoning used up the whole `summaryMaxTokens`. The summary request now turns reasoning off on `dmr` and `llamacpp`. When a summary still comes back empty, the error says why.
10+
11+## New
12+
13+- **Session files** now record the `provider` and the `model`, and each recorded error names its model.
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.2.1
2+
3+*[Version française](v0.2.1.fr.md)*
4+
5+Compression that works with thinking models.
6+
7+## Fixed
8+
9+- **Empty compression summaries with a thinking model**: the reasoning used up the whole `summaryMaxTokens`. The summary request now turns reasoning off on `dmr` and `llamacpp`. When a summary still comes back empty, the error says why.
10+
11+## New
12+
13+- **Session files** now record the `provider` and the `model`, and each recorded error names its model.
added release-notes/v0.2.1.fr.md +13 -0
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.2.1
2+
3+*[English version](v0.2.1.en.md)*
4+
5+Une compression qui marche avec les modèles qui réfléchissent.
6+
7+## Corrigé
8+
9+- **Résumés de compression vides avec un modèle qui réfléchit** : le raisonnement consommait tout `summaryMaxTokens`. La demande de résumé coupe maintenant le raisonnement sur `dmr` et `llamacpp`. Quand un résumé revient quand même vide, l'erreur dit pourquoi.
10+
11+## Nouveautés
12+
13+- **Les fichiers de session** enregistrent maintenant le `provider` et le `model`, et chaque erreur enregistrée nomme son modèle.
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.2.1
2+
3+*[English version](v0.2.1.en.md)*
4+
5+Une compression qui marche avec les modèles qui réfléchissent.
6+
7+## Corrigé
8+
9+- **Résumés de compression vides avec un modèle qui réfléchit** : le raisonnement consommait tout `summaryMaxTokens`. La demande de résumé coupe maintenant le raisonnement sur `dmr` et `llamacpp`. Quand un résumé revient quand même vide, l'erreur dit pourquoi.
10+
11+## Nouveautés
12+
13+- **Les fichiers de session** enregistrent maintenant le `provider` et le `model`, et chaque erreur enregistrée nomme son modèle.
added release-notes/v0.2.2.en.md +11 -0
new file mode 100644
@@ -0,0 +1,11 @@
1+# mini-me v0.2.2
2+
3+*[Version française](v0.2.2.fr.md)*
4+
5+**No change to mm's code.** Sample configurations and Docker Sandbox kits v3.
6+
7+## New
8+
9+- **`config.samples/`**: five configurations tuned for one model each (Gemma, gpt-oss, Qwen3.6 on DMR; Ornith and Qwen3.8 on llama.cpp).
10+- **`docker-sbx-v3/`**: the two kits (`mm-tui`, `mm-acp`) in the sandbox-kit v3 format, with build, check (`kit-tck`) and publish scripts. `docker-sbx/` (v2) is unchanged.
11+- **The kit directories are no longer ignored by git.**
new file mode 100644
@@ -0,0 +1,11 @@
1+# mini-me v0.2.2
2+
3+*[Version française](v0.2.2.fr.md)*
4+
5+**No change to mm's code.** Sample configurations and Docker Sandbox kits v3.
6+
7+## New
8+
9+- **`config.samples/`**: five configurations tuned for one model each (Gemma, gpt-oss, Qwen3.6 on DMR; Ornith and Qwen3.8 on llama.cpp).
10+- **`docker-sbx-v3/`**: the two kits (`mm-tui`, `mm-acp`) in the sandbox-kit v3 format, with build, check (`kit-tck`) and publish scripts. `docker-sbx/` (v2) is unchanged.
11+- **The kit directories are no longer ignored by git.**
added release-notes/v0.2.2.fr.md +11 -0
new file mode 100644
@@ -0,0 +1,11 @@
1+# mini-me v0.2.2
2+
3+*[English version](v0.2.2.en.md)*
4+
5+**Aucun changement dans le code de mm.** Des exemples de configuration et les kits Docker Sandbox v3.
6+
7+## Nouveautés
8+
9+- **`config.samples/`** : cinq configurations réglées chacune pour un modèle (Gemma, gpt-oss, Qwen3.6 sur DMR ; Ornith et Qwen3.8 sur llama.cpp).
10+- **`docker-sbx-v3/`** : les deux kits (`mm-tui`, `mm-acp`) au format sandbox-kit v3, avec des scripts de construction, de vérification (`kit-tck`) et de publication. `docker-sbx/` (v2) ne change pas.
11+- **Les répertoires des kits ne sont plus ignorés par git.**
new file mode 100644
@@ -0,0 +1,11 @@
1+# mini-me v0.2.2
2+
3+*[English version](v0.2.2.en.md)*
4+
5+**Aucun changement dans le code de mm.** Des exemples de configuration et les kits Docker Sandbox v3.
6+
7+## Nouveautés
8+
9+- **`config.samples/`** : cinq configurations réglées chacune pour un modèle (Gemma, gpt-oss, Qwen3.6 sur DMR ; Ornith et Qwen3.8 sur llama.cpp).
10+- **`docker-sbx-v3/`** : les deux kits (`mm-tui`, `mm-acp`) au format sandbox-kit v3, avec des scripts de construction, de vérification (`kit-tck`) et de publication. `docker-sbx/` (v2) ne change pas.
11+- **Les répertoires des kits ne sont plus ignorés par git.**
added release-notes/v0.2.3.en.md +17 -0
new file mode 100644
@@ -0,0 +1,17 @@
1+# mini-me v0.2.3
2+
3+*[Version française](v0.2.3.fr.md)*
4+
5+A built-in tool that reads web pages as Markdown.
6+
7+## New
8+
9+- **`fetch_content`**: fetches a URL and converts the page to Markdown. It is turned on with **`fetchContentTool`**, off by default because it is the only built-in tool that reaches the network. The shipped `agent.dmr.yaml` turns it on.
10+ - **`output`** saves the Markdown to a file and returns one line instead of the page, so the page does not fill the context.
11+ - **Other options**: `baseUrl`, `tables` (`html` or `flatten`), `strikethrough` and `taskList`.
12+ - **Encodings** are detected and the text is converted to UTF-8.
13+ - **`robots.txt` is respected.**
14+ - **URLs are repaired**: `https//…` and a bare host both work.
15+ - **Errors** come back as `[fetch_content: error N (class) — …]`.
16+ - **Permission**: asked only when the tool writes a file.
17+- **New how-to guide and explanation** (EN + FR): Fetch a web page.
new file mode 100644
@@ -0,0 +1,17 @@
1+# mini-me v0.2.3
2+
3+*[Version française](v0.2.3.fr.md)*
4+
5+A built-in tool that reads web pages as Markdown.
6+
7+## New
8+
9+- **`fetch_content`**: fetches a URL and converts the page to Markdown. It is turned on with **`fetchContentTool`**, off by default because it is the only built-in tool that reaches the network. The shipped `agent.dmr.yaml` turns it on.
10+ - **`output`** saves the Markdown to a file and returns one line instead of the page, so the page does not fill the context.
11+ - **Other options**: `baseUrl`, `tables` (`html` or `flatten`), `strikethrough` and `taskList`.
12+ - **Encodings** are detected and the text is converted to UTF-8.
13+ - **`robots.txt` is respected.**
14+ - **URLs are repaired**: `https//…` and a bare host both work.
15+ - **Errors** come back as `[fetch_content: error N (class) — …]`.
16+ - **Permission**: asked only when the tool writes a file.
17+- **New how-to guide and explanation** (EN + FR): Fetch a web page.
added release-notes/v0.2.3.fr.md +17 -0
new file mode 100644
@@ -0,0 +1,17 @@
1+# mini-me v0.2.3
2+
3+*[English version](v0.2.3.en.md)*
4+
5+Un outil intégré qui lit les pages web en Markdown.
6+
7+## Nouveautés
8+
9+- **`fetch_content`** : récupère une URL et convertit la page en Markdown. Il s'active avec **`fetchContentTool`**, désactivé par défaut parce que c'est le seul outil intégré qui va sur le réseau. L'`agent.dmr.yaml` livré l'active.
10+ - **`output`** enregistre le Markdown dans un fichier et renvoie une seule ligne au lieu de la page, pour que la page ne remplisse pas le contexte.
11+ - **Autres options** : `baseUrl`, `tables` (`html` ou `flatten`), `strikethrough` et `taskList`.
12+ - **Les encodages** sont détectés et le texte est converti en UTF-8.
13+ - **`robots.txt` est respecté.**
14+ - **Les URL sont réparées** : `https//…` et un simple nom d'hôte marchent tous les deux.
15+ - **Les erreurs** reviennent sous la forme `[fetch_content: error N (class) — …]`.
16+ - **Permission** : demandée seulement quand l'outil écrit un fichier.
17+- **Nouveau guide pratique et nouvelle explication** (EN + FR) : Récupérer une page web.
new file mode 100644
@@ -0,0 +1,17 @@
1+# mini-me v0.2.3
2+
3+*[English version](v0.2.3.en.md)*
4+
5+Un outil intégré qui lit les pages web en Markdown.
6+
7+## Nouveautés
8+
9+- **`fetch_content`** : récupère une URL et convertit la page en Markdown. Il s'active avec **`fetchContentTool`**, désactivé par défaut parce que c'est le seul outil intégré qui va sur le réseau. L'`agent.dmr.yaml` livré l'active.
10+ - **`output`** enregistre le Markdown dans un fichier et renvoie une seule ligne au lieu de la page, pour que la page ne remplisse pas le contexte.
11+ - **Autres options** : `baseUrl`, `tables` (`html` ou `flatten`), `strikethrough` et `taskList`.
12+ - **Les encodages** sont détectés et le texte est converti en UTF-8.
13+ - **`robots.txt` est respecté.**
14+ - **Les URL sont réparées** : `https//…` et un simple nom d'hôte marchent tous les deux.
15+ - **Les erreurs** reviennent sous la forme `[fetch_content: error N (class) — …]`.
16+ - **Permission** : demandée seulement quand l'outil écrit un fichier.
17+- **Nouveau guide pratique et nouvelle explication** (EN + FR) : Récupérer une page web.
added release-notes/v0.2.4.en.md +15 -0
new file mode 100644
@@ -0,0 +1,15 @@
1+# mini-me v0.2.4
2+
3+*[Version française](v0.2.4.fr.md)*
4+
5+The server's prompt cache works again from one question to the next.
6+
7+## Fixed
8+
9+- **The tool order no longer changes on every question** (#53): Genkit built the tool list from a map, so the order changed at each question. mm now sorts the tools by name before every model call.
10+ - On llama.cpp, from the second question onwards, about 3,400–3,900 tokens re-read in 7–9 s became about 20 tokens in 250 ms (measured on Mellum 12B).
11+
12+## Docs
13+
14+- **New how-to guide** (EN + FR): Verify the tool order. The scripts are in `tests/tool-order/`.
15+- **`config.samples/`**: the configurations turn on `fetchContentTool`.
new file mode 100644
@@ -0,0 +1,15 @@
1+# mini-me v0.2.4
2+
3+*[Version française](v0.2.4.fr.md)*
4+
5+The server's prompt cache works again from one question to the next.
6+
7+## Fixed
8+
9+- **The tool order no longer changes on every question** (#53): Genkit built the tool list from a map, so the order changed at each question. mm now sorts the tools by name before every model call.
10+ - On llama.cpp, from the second question onwards, about 3,400–3,900 tokens re-read in 7–9 s became about 20 tokens in 250 ms (measured on Mellum 12B).
11+
12+## Docs
13+
14+- **New how-to guide** (EN + FR): Verify the tool order. The scripts are in `tests/tool-order/`.
15+- **`config.samples/`**: the configurations turn on `fetchContentTool`.
added release-notes/v0.2.4.fr.md +15 -0
new file mode 100644
@@ -0,0 +1,15 @@
1+# mini-me v0.2.4
2+
3+*[English version](v0.2.4.en.md)*
4+
5+Le cache de prompt du serveur fonctionne de nouveau d'une question à l'autre.
6+
7+## Corrigé
8+
9+- **L'ordre des outils ne change plus à chaque question** (#53) : Genkit construisait la liste des outils à partir d'une map, donc l'ordre changeait à chaque question. mm trie maintenant les outils par nom avant chaque appel au modèle.
10+ - Sur llama.cpp, à partir de la deuxième question, environ 3 400 à 3 900 tokens relus en 7 à 9 s sont devenus environ 20 tokens en 250 ms (mesuré sur Mellum 12B).
11+
12+## Documentation
13+
14+- **Nouveau guide pratique** (EN + FR) : Vérifier l'ordre des outils. Les scripts sont dans `tests/tool-order/`.
15+- **`config.samples/`** : les configurations activent `fetchContentTool`.
new file mode 100644
@@ -0,0 +1,15 @@
1+# mini-me v0.2.4
2+
3+*[English version](v0.2.4.en.md)*
4+
5+Le cache de prompt du serveur fonctionne de nouveau d'une question à l'autre.
6+
7+## Corrigé
8+
9+- **L'ordre des outils ne change plus à chaque question** (#53) : Genkit construisait la liste des outils à partir d'une map, donc l'ordre changeait à chaque question. mm trie maintenant les outils par nom avant chaque appel au modèle.
10+ - Sur llama.cpp, à partir de la deuxième question, environ 3 400 à 3 900 tokens relus en 7 à 9 s sont devenus environ 20 tokens en 250 ms (mesuré sur Mellum 12B).
11+
12+## Documentation
13+
14+- **Nouveau guide pratique** (EN + FR) : Vérifier l'ordre des outils. Les scripts sont dans `tests/tool-order/`.
15+- **`config.samples/`** : les configurations activent `fetchContentTool`.
added release-notes/v0.2.5.en.md +13 -0
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.2.5
2+
3+*[Version française](v0.2.5.fr.md)*
4+
5+Two new model servers: llmman and Ollama.
6+
7+## New
8+
9+- **`llmman` provider** (#60). The context window comes from `/api/ps` (the window actually served). Prompt progress keeps the turn alive while the server reads the prompt.
10+- **`ollama` provider** (#62).
11+- **Reasoning off in compression summaries** is set per provider: Ollama ignores `chat_template_kwargs`, so it gets its own setting.
12+- **Demos**: `demo-llmman/` (Qwen3.5 4B) and `demo-ollama/` (qwen3.5:2b).
13+- **New how-to guides** (EN + FR): Run with llmman, Run with Ollama.
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.2.5
2+
3+*[Version française](v0.2.5.fr.md)*
4+
5+Two new model servers: llmman and Ollama.
6+
7+## New
8+
9+- **`llmman` provider** (#60). The context window comes from `/api/ps` (the window actually served). Prompt progress keeps the turn alive while the server reads the prompt.
10+- **`ollama` provider** (#62).
11+- **Reasoning off in compression summaries** is set per provider: Ollama ignores `chat_template_kwargs`, so it gets its own setting.
12+- **Demos**: `demo-llmman/` (Qwen3.5 4B) and `demo-ollama/` (qwen3.5:2b).
13+- **New how-to guides** (EN + FR): Run with llmman, Run with Ollama.
added release-notes/v0.2.5.fr.md +13 -0
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.2.5
2+
3+*[English version](v0.2.5.en.md)*
4+
5+Deux nouveaux serveurs de modèles : llmman et Ollama.
6+
7+## Nouveautés
8+
9+- **Fournisseur `llmman`** (#60). La fenêtre de contexte vient de `/api/ps` (la fenêtre réellement servie). La progression du prompt garde le tour en vie pendant que le serveur lit le prompt.
10+- **Fournisseur `ollama`** (#62).
11+- **Raisonnement coupé dans les résumés de compression** : réglé par fournisseur, car Ollama ignore `chat_template_kwargs` et a donc son propre réglage.
12+- **Démos** : `demo-llmman/` (Qwen3.5 4B) et `demo-ollama/` (qwen3.5:2b).
13+- **Nouveaux guides pratiques** (EN + FR) : Lancer avec llmman, Lancer avec Ollama.
new file mode 100644
@@ -0,0 +1,13 @@
1+# mini-me v0.2.5
2+
3+*[English version](v0.2.5.en.md)*
4+
5+Deux nouveaux serveurs de modèles : llmman et Ollama.
6+
7+## Nouveautés
8+
9+- **Fournisseur `llmman`** (#60). La fenêtre de contexte vient de `/api/ps` (la fenêtre réellement servie). La progression du prompt garde le tour en vie pendant que le serveur lit le prompt.
10+- **Fournisseur `ollama`** (#62).
11+- **Raisonnement coupé dans les résumés de compression** : réglé par fournisseur, car Ollama ignore `chat_template_kwargs` et a donc son propre réglage.
12+- **Démos** : `demo-llmman/` (Qwen3.5 4B) et `demo-ollama/` (qwen3.5:2b).
13+- **Nouveaux guides pratiques** (EN + FR) : Lancer avec llmman, Lancer avec Ollama.
added release-notes/v0.2.6.en.md +38 -0
new file mode 100644
@@ -0,0 +1,38 @@
1+# mini-me v0.2.6
2+
3+*[Version française](v0.2.6.fr.md)*
4+
5+An agent can ask another mm agent.
6+
7+## New
8+
9+### Asking another agent (#66)
10+
11+- **`mmConfigs`**: a map of agent names to configuration files.
12+- **Commands**:
13+ - `/ask-<name> <prompt>` sends the question alone;
14+ - `/ask-with-context-<name> <prompt>` sends a transcript of the conversation first;
15+ - in the terminal and the window, the generic `/ask <name>` works too.
16+- **How the asked agent runs**:
17+ - each ask starts a fresh child `mm -acp`;
18+ - its answer is streamed and kept in the history;
19+ - its tool calls show as `<name> · <tool>`, and its permission requests go through your front end.
20+- **Limits**: the depth is limited to 2 (`MM_ASK_DEPTH`). Asked agents do not save sessions (`AGENT_SESSIONS=off`).
21+
22+### From a skill or a script (#73)
23+
24+- **`mm --ask <name> -p "…"`** and **`--ask-with-context <name>`**: one question, and only the answer on stdout.
25+- **`MM_BIN` and `MM_CONFIG`** are set for the commands `bash` runs, so a skill can call `"$MM_BIN" --ask …`.
26+
27+### Configuration
28+
29+- **`bashTimeout`** (default `30s`): how long one `bash` command may run.
30+
31+## Demos
32+
33+- **`demo-llmman/`**: two other agents (Jan, Lucy), a reviewer, and the skills `delegate-to-jan`, `second-opinion` and `compare-agents`.
34+
35+## Docs
36+
37+- **New how-to guides** (EN + FR): Ask another agent, Ask another agent from a skill.
38+- **New explanation** (EN + FR): Asking another agent.
new file mode 100644
@@ -0,0 +1,38 @@
1+# mini-me v0.2.6
2+
3+*[Version française](v0.2.6.fr.md)*
4+
5+An agent can ask another mm agent.
6+
7+## New
8+
9+### Asking another agent (#66)
10+
11+- **`mmConfigs`**: a map of agent names to configuration files.
12+- **Commands**:
13+ - `/ask-<name> <prompt>` sends the question alone;
14+ - `/ask-with-context-<name> <prompt>` sends a transcript of the conversation first;
15+ - in the terminal and the window, the generic `/ask <name>` works too.
16+- **How the asked agent runs**:
17+ - each ask starts a fresh child `mm -acp`;
18+ - its answer is streamed and kept in the history;
19+ - its tool calls show as `<name> · <tool>`, and its permission requests go through your front end.
20+- **Limits**: the depth is limited to 2 (`MM_ASK_DEPTH`). Asked agents do not save sessions (`AGENT_SESSIONS=off`).
21+
22+### From a skill or a script (#73)
23+
24+- **`mm --ask <name> -p "…"`** and **`--ask-with-context <name>`**: one question, and only the answer on stdout.
25+- **`MM_BIN` and `MM_CONFIG`** are set for the commands `bash` runs, so a skill can call `"$MM_BIN" --ask …`.
26+
27+### Configuration
28+
29+- **`bashTimeout`** (default `30s`): how long one `bash` command may run.
30+
31+## Demos
32+
33+- **`demo-llmman/`**: two other agents (Jan, Lucy), a reviewer, and the skills `delegate-to-jan`, `second-opinion` and `compare-agents`.
34+
35+## Docs
36+
37+- **New how-to guides** (EN + FR): Ask another agent, Ask another agent from a skill.
38+- **New explanation** (EN + FR): Asking another agent.
added release-notes/v0.2.6.fr.md +38 -0
new file mode 100644
@@ -0,0 +1,38 @@
1+# mini-me v0.2.6
2+
3+*[English version](v0.2.6.en.md)*
4+
5+Un agent peut interroger un autre agent mm.
6+
7+## Nouveautés
8+
9+### Interroger un autre agent (#66)
10+
11+- **`mmConfigs`** : une table qui associe des noms d'agents à des fichiers de configuration.
12+- **Commandes** :
13+ - `/ask-<nom> <prompt>` envoie la question seule ;
14+ - `/ask-with-context-<nom> <prompt>` envoie d'abord une transcription de la conversation ;
15+ - dans le terminal et la fenêtre, le `/ask <nom>` générique marche aussi.
16+- **Comment tourne l'agent interrogé** :
17+ - chaque question lance un nouveau `mm -acp` enfant ;
18+ - sa réponse s'affiche en direct et reste dans l'historique ;
19+ - ses appels d'outil s'affichent sous la forme `<nom> · <outil>`, et ses demandes de permission passent par votre interface.
20+- **Limites** : la profondeur est limitée à 2 (`MM_ASK_DEPTH`). Les agents interrogés n'enregistrent pas de session (`AGENT_SESSIONS=off`).
21+
22+### Depuis un skill ou un script (#73)
23+
24+- **`mm --ask <nom> -p "…"`** et **`--ask-with-context <nom>`** : une question, et seulement la réponse sur stdout.
25+- **`MM_BIN` et `MM_CONFIG`** sont définies pour les commandes que lance `bash` : un skill peut appeler `"$MM_BIN" --ask …`.
26+
27+### Configuration
28+
29+- **`bashTimeout`** (`30s` par défaut) : la durée maximale d'une commande `bash`.
30+
31+## Démos
32+
33+- **`demo-llmman/`** : deux autres agents (Jan, Lucy), un relecteur, et les skills `delegate-to-jan`, `second-opinion` et `compare-agents`.
34+
35+## Documentation
36+
37+- **Nouveaux guides pratiques** (EN + FR) : Interroger un autre agent, Interroger un autre agent depuis un skill.
38+- **Nouvelle explication** (EN + FR) : Interroger un autre agent.
new file mode 100644
@@ -0,0 +1,38 @@
1+# mini-me v0.2.6
2+
3+*[English version](v0.2.6.en.md)*
4+
5+Un agent peut interroger un autre agent mm.
6+
7+## Nouveautés
8+
9+### Interroger un autre agent (#66)
10+
11+- **`mmConfigs`** : une table qui associe des noms d'agents à des fichiers de configuration.
12+- **Commandes** :
13+ - `/ask-<nom> <prompt>` envoie la question seule ;
14+ - `/ask-with-context-<nom> <prompt>` envoie d'abord une transcription de la conversation ;
15+ - dans le terminal et la fenêtre, le `/ask <nom>` générique marche aussi.
16+- **Comment tourne l'agent interrogé** :
17+ - chaque question lance un nouveau `mm -acp` enfant ;
18+ - sa réponse s'affiche en direct et reste dans l'historique ;
19+ - ses appels d'outil s'affichent sous la forme `<nom> · <outil>`, et ses demandes de permission passent par votre interface.
20+- **Limites** : la profondeur est limitée à 2 (`MM_ASK_DEPTH`). Les agents interrogés n'enregistrent pas de session (`AGENT_SESSIONS=off`).
21+
22+### Depuis un skill ou un script (#73)
23+
24+- **`mm --ask <nom> -p "…"`** et **`--ask-with-context <nom>`** : une question, et seulement la réponse sur stdout.
25+- **`MM_BIN` et `MM_CONFIG`** sont définies pour les commandes que lance `bash` : un skill peut appeler `"$MM_BIN" --ask …`.
26+
27+### Configuration
28+
29+- **`bashTimeout`** (`30s` par défaut) : la durée maximale d'une commande `bash`.
30+
31+## Démos
32+
33+- **`demo-llmman/`** : deux autres agents (Jan, Lucy), un relecteur, et les skills `delegate-to-jan`, `second-opinion` et `compare-agents`.
34+
35+## Documentation
36+
37+- **Nouveaux guides pratiques** (EN + FR) : Interroger un autre agent, Interroger un autre agent depuis un skill.
38+- **Nouvelle explication** (EN + FR) : Interroger un autre agent.
added release-notes/v0.3.0.en.md +61 -0
new file mode 100644
@@ -0,0 +1,61 @@
1+# mini-me v0.3.0
2+
3+*[Version française](v0.3.0.fr.md)*
4+
5+Yolo mode, Genkit 1.13.1, an `ask_agent` tool, traces of model traffic, JSON answers checked against Go types, and `AGENTS.md`.
6+
7+## New
8+
9+### Yolo mode: confirm tool calls or not (#13, #59)
10+
11+- **`yolo`** (default `true`, overridden by `AGENT_YOLO`):
12+ - `true`: tools run without asking;
13+ - `false`: every call that changes something asks first, in every front end, including the line mode and an editor.
14+- **Four answers**: `a` once, `s` for the session, `r` reject this call, `n` never.
15+- **`-p` and `--ask` cannot ask**, so with `yolo: false` they refuse the call and say so.
16+
17+### Agents calling agents
18+
19+- **`ask_agent`** is a built-in tool: the model decides itself when to hand a task to an agent of `mmConfigs`. It takes `agent`, `prompt` and `with_context`.
20+- **`mmConfigs` long form**: `{path, description, yolo}`.
21+ - The description feeds the tool.
22+ - A per-agent `yolo` overrides the asker's.
23+- **Window labels**: the window labels answers and its header with `acp.title`, and switches the header to the asked agent while it works.
24+
25+### Model traffic
26+
27+- **`trace`** (`trace.enabled`, `trace.dir`, `AGENT_TRACE`) writes every request and response to `.mm/traces`. Headers are never written, so no API key ends up in the files.
28+- **Genkit Go 1.13.1** (#76). Extra `sampling` keys now reach the server. For example, `chat_template_kwargs: {enable_thinking: false}` turns a model's reasoning off (#67). Compression summaries always turn reasoning off.
29+
30+### JSON answers: `structuredOutput` (#80)
31+
32+- **Declare the answer's shape as Go types** in the YAML. mm sends the matching JSON Schema; with `-p` and `--ask`, only the JSON is printed on stdout.
33+ - Comments become descriptions, and pointers or `omitempty` make a field optional.
34+ - Unsupported constructs are configuration errors, with their line and column.
35+
36+### Project instructions: `AGENTS.md` (#81)
37+
38+- **mm reads `AGENTS.md`** from the working directory into the system prompt, under `PROJECT INSTRUCTIONS`. It is read at start-up, on `/new` and for each new editor session.
39+- **`agentsMd`** settings: `enabled` (on by default), `path` and `maxChars`.
40+- **The banner** shows the file and warns when it takes 20 % or more of the context window.
41+
42+## Fixed
43+
44+- **Start-up errors now go to stderr**, so `mm -p … | jq` is not fed an error message.
45+
46+## Demos
47+
48+- **`mini-mes/gopher`**: a Go specialist.
49+- **`mini-mes/riker`**: a coordinator, a specifier and a coder.
50+- **`demo-structured-output/`** and **`demo-agents-file/`**.
51+- **`demo/` is renamed `demo-dmr/`.**
52+
53+## Docs
54+
55+- **New how-to guides** (EN + FR): Confirm tool calls, Turn reasoning off, Let the agent call other agents, Inspect model traffic, Get JSON answers, Give project instructions.
56+- **New explanations** (EN + FR): yolo and permissions, structured output, project instructions.
57+
58+## Upgrading
59+
60+- **`permissions` is replaced by `yolo`.** `permissions` is still read, with a deprecation warning.
61+- **Editors no longer show a permission dialog by default**, since `yolo` defaults to `true`. Set `yolo: false` to get the dialogs back.
new file mode 100644
@@ -0,0 +1,61 @@
1+# mini-me v0.3.0
2+
3+*[Version française](v0.3.0.fr.md)*
4+
5+Yolo mode, Genkit 1.13.1, an `ask_agent` tool, traces of model traffic, JSON answers checked against Go types, and `AGENTS.md`.
6+
7+## New
8+
9+### Yolo mode: confirm tool calls or not (#13, #59)
10+
11+- **`yolo`** (default `true`, overridden by `AGENT_YOLO`):
12+ - `true`: tools run without asking;
13+ - `false`: every call that changes something asks first, in every front end, including the line mode and an editor.
14+- **Four answers**: `a` once, `s` for the session, `r` reject this call, `n` never.
15+- **`-p` and `--ask` cannot ask**, so with `yolo: false` they refuse the call and say so.
16+
17+### Agents calling agents
18+
19+- **`ask_agent`** is a built-in tool: the model decides itself when to hand a task to an agent of `mmConfigs`. It takes `agent`, `prompt` and `with_context`.
20+- **`mmConfigs` long form**: `{path, description, yolo}`.
21+ - The description feeds the tool.
22+ - A per-agent `yolo` overrides the asker's.
23+- **Window labels**: the window labels answers and its header with `acp.title`, and switches the header to the asked agent while it works.
24+
25+### Model traffic
26+
27+- **`trace`** (`trace.enabled`, `trace.dir`, `AGENT_TRACE`) writes every request and response to `.mm/traces`. Headers are never written, so no API key ends up in the files.
28+- **Genkit Go 1.13.1** (#76). Extra `sampling` keys now reach the server. For example, `chat_template_kwargs: {enable_thinking: false}` turns a model's reasoning off (#67). Compression summaries always turn reasoning off.
29+
30+### JSON answers: `structuredOutput` (#80)
31+
32+- **Declare the answer's shape as Go types** in the YAML. mm sends the matching JSON Schema; with `-p` and `--ask`, only the JSON is printed on stdout.
33+ - Comments become descriptions, and pointers or `omitempty` make a field optional.
34+ - Unsupported constructs are configuration errors, with their line and column.
35+
36+### Project instructions: `AGENTS.md` (#81)
37+
38+- **mm reads `AGENTS.md`** from the working directory into the system prompt, under `PROJECT INSTRUCTIONS`. It is read at start-up, on `/new` and for each new editor session.
39+- **`agentsMd`** settings: `enabled` (on by default), `path` and `maxChars`.
40+- **The banner** shows the file and warns when it takes 20 % or more of the context window.
41+
42+## Fixed
43+
44+- **Start-up errors now go to stderr**, so `mm -p … | jq` is not fed an error message.
45+
46+## Demos
47+
48+- **`mini-mes/gopher`**: a Go specialist.
49+- **`mini-mes/riker`**: a coordinator, a specifier and a coder.
50+- **`demo-structured-output/`** and **`demo-agents-file/`**.
51+- **`demo/` is renamed `demo-dmr/`.**
52+
53+## Docs
54+
55+- **New how-to guides** (EN + FR): Confirm tool calls, Turn reasoning off, Let the agent call other agents, Inspect model traffic, Get JSON answers, Give project instructions.
56+- **New explanations** (EN + FR): yolo and permissions, structured output, project instructions.
57+
58+## Upgrading
59+
60+- **`permissions` is replaced by `yolo`.** `permissions` is still read, with a deprecation warning.
61+- **Editors no longer show a permission dialog by default**, since `yolo` defaults to `true`. Set `yolo: false` to get the dialogs back.
added release-notes/v0.3.0.fr.md +61 -0
new file mode 100644
@@ -0,0 +1,61 @@
1+# mini-me v0.3.0
2+
3+*[English version](v0.3.0.en.md)*
4+
5+Le mode yolo, Genkit 1.13.1, un outil `ask_agent`, les traces des échanges avec le modèle, des réponses JSON vérifiées contre des types Go, et `AGENTS.md`.
6+
7+## Nouveautés
8+
9+### Mode yolo : confirmer ou non les appels d'outil (#13, #59)
10+
11+- **`yolo`** (`true` par défaut, remplacé par `AGENT_YOLO`) :
12+ - `true` : les outils s'exécutent sans demander ;
13+ - `false` : chaque appel qui change quelque chose demande d'abord, dans toutes les interfaces, y compris le mode ligne et un éditeur.
14+- **Quatre réponses** : `a` une fois, `s` pour la session, `r` refuser cet appel, `n` jamais.
15+- **`-p` et `--ask` ne peuvent pas demander** : avec `yolo: false`, ils refusent l'appel et le disent.
16+
17+### Des agents qui appellent des agents
18+
19+- **`ask_agent`** est un outil intégré : le modèle décide lui-même quand confier une tâche à un agent de `mmConfigs`. Il prend `agent`, `prompt` et `with_context`.
20+- **Forme longue de `mmConfigs`** : `{path, description, yolo}`.
21+ - La description alimente l'outil.
22+ - Un `yolo` par agent l'emporte sur celui de l'agent qui interroge.
23+- **Étiquettes de la fenêtre** : la fenêtre étiquette les réponses et son en-tête avec `acp.title`, et l'en-tête passe à l'agent interrogé pendant qu'il travaille.
24+
25+### Échanges avec le modèle
26+
27+- **`trace`** (`trace.enabled`, `trace.dir`, `AGENT_TRACE`) écrit chaque requête et chaque réponse dans `.mm/traces`. Les en-têtes ne sont jamais écrits : aucune clé d'API ne se retrouve dans les fichiers.
28+- **Genkit Go 1.13.1** (#76). Les clés `sampling` supplémentaires arrivent maintenant au serveur. Par exemple, `chat_template_kwargs: {enable_thinking: false}` coupe le raisonnement d'un modèle (#67). Les résumés de compression coupent toujours le raisonnement.
29+
30+### Réponses JSON : `structuredOutput` (#80)
31+
32+- **Déclarez la forme de la réponse avec des types Go** dans le YAML. mm envoie le JSON Schema correspondant ; avec `-p` et `--ask`, seul le JSON est écrit sur stdout.
33+ - Les commentaires deviennent des descriptions, et un pointeur ou `omitempty` rend un champ facultatif.
34+ - Les constructions non prises en charge sont des erreurs de configuration, avec leur ligne et leur colonne.
35+
36+### Instructions de projet : `AGENTS.md` (#81)
37+
38+- **mm lit `AGENTS.md`** dans le répertoire de travail et l'ajoute au prompt système, sous `PROJECT INSTRUCTIONS`. Il est lu au démarrage, à `/new` et pour chaque nouvelle session d'éditeur.
39+- **Réglages `agentsMd`** : `enabled` (activé par défaut), `path` et `maxChars`.
40+- **La bannière** affiche le fichier et prévient quand il occupe 20 % ou plus de la fenêtre de contexte.
41+
42+## Corrigé
43+
44+- **Les erreurs de démarrage vont maintenant sur stderr** : `mm -p … | jq` ne reçoit plus un message d'erreur.
45+
46+## Démos
47+
48+- **`mini-mes/gopher`** : un spécialiste Go.
49+- **`mini-mes/riker`** : un coordinateur, un spécificateur et un codeur.
50+- **`demo-structured-output/`** et **`demo-agents-file/`**.
51+- **`demo/` est renommé `demo-dmr/`.**
52+
53+## Documentation
54+
55+- **Nouveaux guides pratiques** (EN + FR) : Confirmer les appels d'outil, Couper le raisonnement, Laisser l'agent appeler d'autres agents, Inspecter les échanges avec le modèle, Obtenir des réponses JSON, Donner des instructions de projet.
56+- **Nouvelles explications** (EN + FR) : yolo et permissions, sortie structurée, instructions de projet.
57+
58+## Mise à jour
59+
60+- **`permissions` est remplacé par `yolo`.** `permissions` est encore lu, avec un avertissement d'obsolescence.
61+- **Les éditeurs n'affichent plus de demande de permission par défaut**, puisque `yolo` vaut `true` par défaut. Mettez `yolo: false` pour retrouver les demandes.
new file mode 100644
@@ -0,0 +1,61 @@
1+# mini-me v0.3.0
2+
3+*[English version](v0.3.0.en.md)*
4+
5+Le mode yolo, Genkit 1.13.1, un outil `ask_agent`, les traces des échanges avec le modèle, des réponses JSON vérifiées contre des types Go, et `AGENTS.md`.
6+
7+## Nouveautés
8+
9+### Mode yolo : confirmer ou non les appels d'outil (#13, #59)
10+
11+- **`yolo`** (`true` par défaut, remplacé par `AGENT_YOLO`) :
12+ - `true` : les outils s'exécutent sans demander ;
13+ - `false` : chaque appel qui change quelque chose demande d'abord, dans toutes les interfaces, y compris le mode ligne et un éditeur.
14+- **Quatre réponses** : `a` une fois, `s` pour la session, `r` refuser cet appel, `n` jamais.
15+- **`-p` et `--ask` ne peuvent pas demander** : avec `yolo: false`, ils refusent l'appel et le disent.
16+
17+### Des agents qui appellent des agents
18+
19+- **`ask_agent`** est un outil intégré : le modèle décide lui-même quand confier une tâche à un agent de `mmConfigs`. Il prend `agent`, `prompt` et `with_context`.
20+- **Forme longue de `mmConfigs`** : `{path, description, yolo}`.
21+ - La description alimente l'outil.
22+ - Un `yolo` par agent l'emporte sur celui de l'agent qui interroge.
23+- **Étiquettes de la fenêtre** : la fenêtre étiquette les réponses et son en-tête avec `acp.title`, et l'en-tête passe à l'agent interrogé pendant qu'il travaille.
24+
25+### Échanges avec le modèle
26+
27+- **`trace`** (`trace.enabled`, `trace.dir`, `AGENT_TRACE`) écrit chaque requête et chaque réponse dans `.mm/traces`. Les en-têtes ne sont jamais écrits : aucune clé d'API ne se retrouve dans les fichiers.
28+- **Genkit Go 1.13.1** (#76). Les clés `sampling` supplémentaires arrivent maintenant au serveur. Par exemple, `chat_template_kwargs: {enable_thinking: false}` coupe le raisonnement d'un modèle (#67). Les résumés de compression coupent toujours le raisonnement.
29+
30+### Réponses JSON : `structuredOutput` (#80)
31+
32+- **Déclarez la forme de la réponse avec des types Go** dans le YAML. mm envoie le JSON Schema correspondant ; avec `-p` et `--ask`, seul le JSON est écrit sur stdout.
33+ - Les commentaires deviennent des descriptions, et un pointeur ou `omitempty` rend un champ facultatif.
34+ - Les constructions non prises en charge sont des erreurs de configuration, avec leur ligne et leur colonne.
35+
36+### Instructions de projet : `AGENTS.md` (#81)
37+
38+- **mm lit `AGENTS.md`** dans le répertoire de travail et l'ajoute au prompt système, sous `PROJECT INSTRUCTIONS`. Il est lu au démarrage, à `/new` et pour chaque nouvelle session d'éditeur.
39+- **Réglages `agentsMd`** : `enabled` (activé par défaut), `path` et `maxChars`.
40+- **La bannière** affiche le fichier et prévient quand il occupe 20 % ou plus de la fenêtre de contexte.
41+
42+## Corrigé
43+
44+- **Les erreurs de démarrage vont maintenant sur stderr** : `mm -p … | jq` ne reçoit plus un message d'erreur.
45+
46+## Démos
47+
48+- **`mini-mes/gopher`** : un spécialiste Go.
49+- **`mini-mes/riker`** : un coordinateur, un spécificateur et un codeur.
50+- **`demo-structured-output/`** et **`demo-agents-file/`**.
51+- **`demo/` est renommé `demo-dmr/`.**
52+
53+## Documentation
54+
55+- **Nouveaux guides pratiques** (EN + FR) : Confirmer les appels d'outil, Couper le raisonnement, Laisser l'agent appeler d'autres agents, Inspecter les échanges avec le modèle, Obtenir des réponses JSON, Donner des instructions de projet.
56+- **Nouvelles explications** (EN + FR) : yolo et permissions, sortie structurée, instructions de projet.
57+
58+## Mise à jour
59+
60+- **`permissions` est remplacé par `yolo`.** `permissions` est encore lu, avec un avertissement d'obsolescence.
61+- **Les éditeurs n'affichent plus de demande de permission par défaut**, puisque `yolo` vaut `true` par défaut. Mettez `yolo: false` pour retrouver les demandes.
added release-notes/v0.4.0.en.md +36 -0
new file mode 100644
@@ -0,0 +1,36 @@
1+# mini-me v0.4.0
2+
3+*[Version française](v0.4.0.fr.md)*
4+
5+The agent can ask you questions, MCP tools come with their parameters, and the demos move under `demos/`.
6+
7+## New
8+
9+- **`ask_user`** (#84): the model asks you up to 4 questions at once: pick one, pick several, type a text, or yes/no.
10+ - **Where it shows**: numbered answers in the line mode, arrow keys in the window, `elicitation/create` in an editor. An editor that does not support elicitation gets a permission dialog instead.
11+ - **Agents you ask**: their questions are relayed to your user.
12+ - **Switches**: `askUserTool` (default `true`), `AGENT_ASK_USER`, and `mmConfigs.<name>.askUser`.
13+ - It is never declared next to a `structuredOutput`.
14+- **Agent creator**: `agent.creator.yaml` and the skill `skills/create-agent`. They write an mm agent tuned to a given model, from the facts of its chat template.
15+
16+## Fixed
17+
18+- **MCP tools are declared with their parameters.** They used to reach the model with an empty schema, so llama.cpp parsed every call as `{}`. The model now sees a lenient copy of the input schema.
19+
20+## Demos
21+
22+- **All demos now live under `demos/`** (`demos/dmr`, `demos/llmman`, `demos/mini-mes`, …).
23+- **New demos**:
24+ - `little-coder` (Go + htmx);
25+ - `team` and `blog-team` (teams of agents);
26+ - `translator`;
27+ - `news-roundup` (#83, an MCP server over DuckDuckGo);
28+ - `project-wizard` (`ask_user`);
29+ - three agents in the style of other coding CLIs: `qwen-style` (Qwen Code's prompt), `gemini-style` (#85, Gemini CLI's prompt) and `codex-style` (#86, Codex CLI's prompt).
30+- **LSP for Go, Rust and Node** in `gemini-style` and `qwen-style`.
31+
32+## Docs
33+
34+- **New how-to guide and explanation** (EN + FR): Let the agent ask you.
35+- **Docker Sandboxes how-to** rewritten: the mini-me kit now lives in its own repository (`mini-me-kit`).
36+- **New `LICENSE` file** (MIT).
new file mode 100644
@@ -0,0 +1,36 @@
1+# mini-me v0.4.0
2+
3+*[Version française](v0.4.0.fr.md)*
4+
5+The agent can ask you questions, MCP tools come with their parameters, and the demos move under `demos/`.
6+
7+## New
8+
9+- **`ask_user`** (#84): the model asks you up to 4 questions at once: pick one, pick several, type a text, or yes/no.
10+ - **Where it shows**: numbered answers in the line mode, arrow keys in the window, `elicitation/create` in an editor. An editor that does not support elicitation gets a permission dialog instead.
11+ - **Agents you ask**: their questions are relayed to your user.
12+ - **Switches**: `askUserTool` (default `true`), `AGENT_ASK_USER`, and `mmConfigs.<name>.askUser`.
13+ - It is never declared next to a `structuredOutput`.
14+- **Agent creator**: `agent.creator.yaml` and the skill `skills/create-agent`. They write an mm agent tuned to a given model, from the facts of its chat template.
15+
16+## Fixed
17+
18+- **MCP tools are declared with their parameters.** They used to reach the model with an empty schema, so llama.cpp parsed every call as `{}`. The model now sees a lenient copy of the input schema.
19+
20+## Demos
21+
22+- **All demos now live under `demos/`** (`demos/dmr`, `demos/llmman`, `demos/mini-mes`, …).
23+- **New demos**:
24+ - `little-coder` (Go + htmx);
25+ - `team` and `blog-team` (teams of agents);
26+ - `translator`;
27+ - `news-roundup` (#83, an MCP server over DuckDuckGo);
28+ - `project-wizard` (`ask_user`);
29+ - three agents in the style of other coding CLIs: `qwen-style` (Qwen Code's prompt), `gemini-style` (#85, Gemini CLI's prompt) and `codex-style` (#86, Codex CLI's prompt).
30+- **LSP for Go, Rust and Node** in `gemini-style` and `qwen-style`.
31+
32+## Docs
33+
34+- **New how-to guide and explanation** (EN + FR): Let the agent ask you.
35+- **Docker Sandboxes how-to** rewritten: the mini-me kit now lives in its own repository (`mini-me-kit`).
36+- **New `LICENSE` file** (MIT).
added release-notes/v0.4.0.fr.md +36 -0
new file mode 100644
@@ -0,0 +1,36 @@
1+# mini-me v0.4.0
2+
3+*[English version](v0.4.0.en.md)*
4+
5+L'agent peut vous poser des questions, les outils MCP arrivent avec leurs paramètres, et les démos passent dans `demos/`.
6+
7+## Nouveautés
8+
9+- **`ask_user`** (#84) : le modèle vous pose jusqu'à 4 questions à la fois : choisir une option, en choisir plusieurs, taper un texte, ou oui/non.
10+ - **Où ça s'affiche** : réponses numérotées en mode ligne, flèches dans la fenêtre, `elicitation/create` dans un éditeur. Un éditeur qui ne prend pas en charge l'élicitation reçoit une demande de permission à la place.
11+ - **Agents interrogés** : leurs questions sont relayées à votre utilisateur.
12+ - **Réglages** : `askUserTool` (`true` par défaut), `AGENT_ASK_USER` et `mmConfigs.<nom>.askUser`.
13+ - Il n'est jamais déclaré à côté d'un `structuredOutput`.
14+- **Créateur d'agents** : `agent.creator.yaml` et le skill `skills/create-agent`. Ils écrivent un agent mm adapté à un modèle donné, à partir des informations de son chat template.
15+
16+## Corrigé
17+
18+- **Les outils MCP sont déclarés avec leurs paramètres.** Ils arrivaient au modèle avec un schéma vide, et llama.cpp lisait donc chaque appel comme `{}`. Le modèle voit maintenant une copie souple du schéma d'entrée.
19+
20+## Démos
21+
22+- **Toutes les démos sont maintenant dans `demos/`** (`demos/dmr`, `demos/llmman`, `demos/mini-mes`, …).
23+- **Nouvelles démos** :
24+ - `little-coder` (Go + htmx) ;
25+ - `team` et `blog-team` (des équipes d'agents) ;
26+ - `translator` ;
27+ - `news-roundup` (#83, un serveur MCP au-dessus de DuckDuckGo) ;
28+ - `project-wizard` (`ask_user`) ;
29+ - trois agents dans le style d'autres CLI de code : `qwen-style` (le prompt de Qwen Code), `gemini-style` (#85, celui de Gemini CLI) et `codex-style` (#86, celui de Codex CLI).
30+- **LSP pour Go, Rust et Node** dans `gemini-style` et `qwen-style`.
31+
32+## Documentation
33+
34+- **Nouveau guide pratique et nouvelle explication** (EN + FR) : Laisser l'agent vous poser des questions.
35+- **Guide Docker Sandboxes** réécrit : le kit mini-me vit maintenant dans son propre dépôt (`mini-me-kit`).
36+- **Nouveau fichier `LICENSE`** (MIT).
new file mode 100644
@@ -0,0 +1,36 @@
1+# mini-me v0.4.0
2+
3+*[English version](v0.4.0.en.md)*
4+
5+L'agent peut vous poser des questions, les outils MCP arrivent avec leurs paramètres, et les démos passent dans `demos/`.
6+
7+## Nouveautés
8+
9+- **`ask_user`** (#84) : le modèle vous pose jusqu'à 4 questions à la fois : choisir une option, en choisir plusieurs, taper un texte, ou oui/non.
10+ - **Où ça s'affiche** : réponses numérotées en mode ligne, flèches dans la fenêtre, `elicitation/create` dans un éditeur. Un éditeur qui ne prend pas en charge l'élicitation reçoit une demande de permission à la place.
11+ - **Agents interrogés** : leurs questions sont relayées à votre utilisateur.
12+ - **Réglages** : `askUserTool` (`true` par défaut), `AGENT_ASK_USER` et `mmConfigs.<nom>.askUser`.
13+ - Il n'est jamais déclaré à côté d'un `structuredOutput`.
14+- **Créateur d'agents** : `agent.creator.yaml` et le skill `skills/create-agent`. Ils écrivent un agent mm adapté à un modèle donné, à partir des informations de son chat template.
15+
16+## Corrigé
17+
18+- **Les outils MCP sont déclarés avec leurs paramètres.** Ils arrivaient au modèle avec un schéma vide, et llama.cpp lisait donc chaque appel comme `{}`. Le modèle voit maintenant une copie souple du schéma d'entrée.
19+
20+## Démos
21+
22+- **Toutes les démos sont maintenant dans `demos/`** (`demos/dmr`, `demos/llmman`, `demos/mini-mes`, …).
23+- **Nouvelles démos** :
24+ - `little-coder` (Go + htmx) ;
25+ - `team` et `blog-team` (des équipes d'agents) ;
26+ - `translator` ;
27+ - `news-roundup` (#83, un serveur MCP au-dessus de DuckDuckGo) ;
28+ - `project-wizard` (`ask_user`) ;
29+ - trois agents dans le style d'autres CLI de code : `qwen-style` (le prompt de Qwen Code), `gemini-style` (#85, celui de Gemini CLI) et `codex-style` (#86, celui de Codex CLI).
30+- **LSP pour Go, Rust et Node** dans `gemini-style` et `qwen-style`.
31+
32+## Documentation
33+
34+- **Nouveau guide pratique et nouvelle explication** (EN + FR) : Laisser l'agent vous poser des questions.
35+- **Guide Docker Sandboxes** réécrit : le kit mini-me vit maintenant dans son propre dépôt (`mini-me-kit`).
36+- **Nouveau fichier `LICENSE`** (MIT).
added release-notes/v0.5.0.en.md +49 -0
new file mode 100644
@@ -0,0 +1,49 @@
1+# mini-me v0.5.0
2+
3+*[Version française](v0.5.0.fr.md)*
4+
5+Hooks: your own scripts around generations, tool calls and calls to other agents.
6+
7+## New
8+
9+### Hooks (#36)
10+
11+- **`hooks:`** runs your own commands at four moments: `preGeneration`, `preToolCall`, `postToolCall` and `postGeneration`.
12+- **What a hook can do**:
13+ - stop a question;
14+ - add context for the model;
15+ - deny or rewrite a tool call;
16+ - add text after a tool's output;
17+ - observe the end of a turn.
18+- **Protocol**: one JSON object on stdin, an optional JSON object on stdout.
19+- **Per hook**: `matcher` (a regular expression on the tool name), `timeout` and `onError`.
20+- **A stopped question is masked** in the history.
21+- **The banner** lists the hooks.
22+
23+### Hooks around calls to other agents (#87)
24+
25+- **`preAsk` and `postAsk`**, declared in the asking agent's file, with a `matcher` on the agent's name. They run for `ask_agent`, `/ask…` and `mm --ask`.
26+ - `preAsk` can deny the call, rewrite the question or the conversation sent, or add context.
27+ - `postAsk` can replace the answer or add context after it.
28+
29+## Fixed
30+
31+- **Hook input JSON is no longer HTML-escaped**: `&&` reached hooks as `&&`, which hid it from a plain text search.
32+- **A call denied by a hook** now names the command in its title. In the line mode, a denied `ask_agent` shows a line.
33+- **In an editor**, the plan tool's "unknown action" message now lists the valid actions.
34+
35+## Security
36+
37+- **Dependency updates** (`golang.org/x/net` 0.61.0, OpenTelemetry 1.47.0) and the toolchain pinned to `go1.26.9`, to clear known CVEs reported by osv-scanner.
38+
39+## Demos
40+
41+- `hooks` (Node.js hooks);
42+- `hooks-ask`;
43+- `dungeon` (a role-playing team on Qwen3.5 4B);
44+- `coding-helpers` (Go and Rust documentation);
45+- `structured-output-pizza` (`structuredOutput` with Go types).
46+
47+## Docs
48+
49+- **New hooks pages** (EN + FR): reference, explanation, and the how-to guides "Guard the agent with hooks" and "Control agent calls with hooks".
new file mode 100644
@@ -0,0 +1,49 @@
1+# mini-me v0.5.0
2+
3+*[Version française](v0.5.0.fr.md)*
4+
5+Hooks: your own scripts around generations, tool calls and calls to other agents.
6+
7+## New
8+
9+### Hooks (#36)
10+
11+- **`hooks:`** runs your own commands at four moments: `preGeneration`, `preToolCall`, `postToolCall` and `postGeneration`.
12+- **What a hook can do**:
13+ - stop a question;
14+ - add context for the model;
15+ - deny or rewrite a tool call;
16+ - add text after a tool's output;
17+ - observe the end of a turn.
18+- **Protocol**: one JSON object on stdin, an optional JSON object on stdout.
19+- **Per hook**: `matcher` (a regular expression on the tool name), `timeout` and `onError`.
20+- **A stopped question is masked** in the history.
21+- **The banner** lists the hooks.
22+
23+### Hooks around calls to other agents (#87)
24+
25+- **`preAsk` and `postAsk`**, declared in the asking agent's file, with a `matcher` on the agent's name. They run for `ask_agent`, `/ask…` and `mm --ask`.
26+ - `preAsk` can deny the call, rewrite the question or the conversation sent, or add context.
27+ - `postAsk` can replace the answer or add context after it.
28+
29+## Fixed
30+
31+- **Hook input JSON is no longer HTML-escaped**: `&&` reached hooks as `&&`, which hid it from a plain text search.
32+- **A call denied by a hook** now names the command in its title. In the line mode, a denied `ask_agent` shows a line.
33+- **In an editor**, the plan tool's "unknown action" message now lists the valid actions.
34+
35+## Security
36+
37+- **Dependency updates** (`golang.org/x/net` 0.61.0, OpenTelemetry 1.47.0) and the toolchain pinned to `go1.26.9`, to clear known CVEs reported by osv-scanner.
38+
39+## Demos
40+
41+- `hooks` (Node.js hooks);
42+- `hooks-ask`;
43+- `dungeon` (a role-playing team on Qwen3.5 4B);
44+- `coding-helpers` (Go and Rust documentation);
45+- `structured-output-pizza` (`structuredOutput` with Go types).
46+
47+## Docs
48+
49+- **New hooks pages** (EN + FR): reference, explanation, and the how-to guides "Guard the agent with hooks" and "Control agent calls with hooks".
added release-notes/v0.5.0.fr.md +49 -0
new file mode 100644
@@ -0,0 +1,49 @@
1+# mini-me v0.5.0
2+
3+*[English version](v0.5.0.en.md)*
4+
5+Les hooks : vos propres scripts autour des générations, des appels d'outil et des appels à d'autres agents.
6+
7+## Nouveautés
8+
9+### Hooks (#36)
10+
11+- **`hooks:`** lance vos propres commandes à quatre moments : `preGeneration`, `preToolCall`, `postToolCall` et `postGeneration`.
12+- **Ce qu'un hook peut faire** :
13+ - arrêter une question ;
14+ - ajouter du contexte pour le modèle ;
15+ - refuser ou réécrire un appel d'outil ;
16+ - ajouter du texte après la sortie d'un outil ;
17+ - observer la fin d'un tour.
18+- **Protocole** : un objet JSON sur stdin, un objet JSON facultatif sur stdout.
19+- **Par hook** : `matcher` (une expression régulière sur le nom de l'outil), `timeout` et `onError`.
20+- **Une question arrêtée est masquée** dans l'historique.
21+- **La bannière** liste les hooks.
22+
23+### Hooks autour des appels à d'autres agents (#87)
24+
25+- **`preAsk` et `postAsk`**, déclarés dans le fichier de l'agent qui interroge, avec un `matcher` sur le nom de l'agent. Ils s'exécutent pour `ask_agent`, `/ask…` et `mm --ask`.
26+ - `preAsk` peut refuser l'appel, réécrire la question ou la conversation envoyée, ou ajouter du contexte.
27+ - `postAsk` peut remplacer la réponse ou ajouter du contexte après elle.
28+
29+## Corrigé
30+
31+- **Le JSON d'entrée des hooks n'est plus échappé pour le HTML** : `&&` arrivait aux hooks sous la forme `&&`, ce qui le cachait à une simple recherche de texte.
32+- **Un appel refusé par un hook** nomme maintenant la commande dans son titre. En mode ligne, un `ask_agent` refusé affiche une ligne.
33+- **Dans un éditeur**, le message « action inconnue » de l'outil de plan liste maintenant les actions valides.
34+
35+## Sécurité
36+
37+- **Mises à jour de dépendances** (`golang.org/x/net` 0.61.0, OpenTelemetry 1.47.0) et toolchain épinglée sur `go1.26.9`, pour corriger les CVE connues signalées par osv-scanner.
38+
39+## Démos
40+
41+- `hooks` (des hooks en Node.js) ;
42+- `hooks-ask` ;
43+- `dungeon` (une équipe de jeu de rôle sur Qwen3.5 4B) ;
44+- `coding-helpers` (documentation Go et Rust) ;
45+- `structured-output-pizza` (`structuredOutput` avec des types Go).
46+
47+## Documentation
48+
49+- **Nouvelles pages sur les hooks** (EN + FR) : la référence, l'explication, et les guides pratiques « Protéger l'agent avec des hooks » et « Contrôler les relais avec des hooks ».
new file mode 100644
@@ -0,0 +1,49 @@
1+# mini-me v0.5.0
2+
3+*[English version](v0.5.0.en.md)*
4+
5+Les hooks : vos propres scripts autour des générations, des appels d'outil et des appels à d'autres agents.
6+
7+## Nouveautés
8+
9+### Hooks (#36)
10+
11+- **`hooks:`** lance vos propres commandes à quatre moments : `preGeneration`, `preToolCall`, `postToolCall` et `postGeneration`.
12+- **Ce qu'un hook peut faire** :
13+ - arrêter une question ;
14+ - ajouter du contexte pour le modèle ;
15+ - refuser ou réécrire un appel d'outil ;
16+ - ajouter du texte après la sortie d'un outil ;
17+ - observer la fin d'un tour.
18+- **Protocole** : un objet JSON sur stdin, un objet JSON facultatif sur stdout.
19+- **Par hook** : `matcher` (une expression régulière sur le nom de l'outil), `timeout` et `onError`.
20+- **Une question arrêtée est masquée** dans l'historique.
21+- **La bannière** liste les hooks.
22+
23+### Hooks autour des appels à d'autres agents (#87)
24+
25+- **`preAsk` et `postAsk`**, déclarés dans le fichier de l'agent qui interroge, avec un `matcher` sur le nom de l'agent. Ils s'exécutent pour `ask_agent`, `/ask…` et `mm --ask`.
26+ - `preAsk` peut refuser l'appel, réécrire la question ou la conversation envoyée, ou ajouter du contexte.
27+ - `postAsk` peut remplacer la réponse ou ajouter du contexte après elle.
28+
29+## Corrigé
30+
31+- **Le JSON d'entrée des hooks n'est plus échappé pour le HTML** : `&&` arrivait aux hooks sous la forme `&&`, ce qui le cachait à une simple recherche de texte.
32+- **Un appel refusé par un hook** nomme maintenant la commande dans son titre. En mode ligne, un `ask_agent` refusé affiche une ligne.
33+- **Dans un éditeur**, le message « action inconnue » de l'outil de plan liste maintenant les actions valides.
34+
35+## Sécurité
36+
37+- **Mises à jour de dépendances** (`golang.org/x/net` 0.61.0, OpenTelemetry 1.47.0) et toolchain épinglée sur `go1.26.9`, pour corriger les CVE connues signalées par osv-scanner.
38+
39+## Démos
40+
41+- `hooks` (des hooks en Node.js) ;
42+- `hooks-ask` ;
43+- `dungeon` (une équipe de jeu de rôle sur Qwen3.5 4B) ;
44+- `coding-helpers` (documentation Go et Rust) ;
45+- `structured-output-pizza` (`structuredOutput` avec des types Go).
46+
47+## Documentation
48+
49+- **Nouvelles pages sur les hooks** (EN + FR) : la référence, l'explication, et les guides pratiques « Protéger l'agent avec des hooks » et « Contrôler les relais avec des hooks ».
added release-notes/v0.6.0.en.md +36 -0
new file mode 100644
@@ -0,0 +1,36 @@
1+# mini-me v0.6.0
2+
3+*[Version française](v0.6.0.fr.md)*
4+
5+Your own tools declared in the agent file, and answers saved to a file without being written again.
6+
7+## New
8+
9+### Declared tools (#88)
10+
11+- **A `tools:` block** declares your own tools. Each one is a command, run as an argument list and **never through a shell**.
12+- **Parameters** can be `string`, `enum`, `boolean`, `integer` or `number`. `"3"` and `"true"` are accepted for numbers and booleans.
13+- **Invalid calls** are answered in text, quoting the call back.
14+- **Consent follows `yolo`.**
15+- **Names**: built-in tool names are reserved, even when the tool is turned off. A clash with an MCP tool is a warning.
16+- Measured with Mellum2 12B and Qwen3.5 4B: 30/30 right calls each, all in a single call.
17+
18+### Save an answer
19+
20+- **`/save <file> [n]`** writes the last answer (or the n-th from the end) to a new file. It never overwrites an existing file.
21+- **`save_answer`**: the model saves an answer it already gave by sending only the file name, not the text again. It is turned on with `saveAnswerTool`, off by default.
22+
23+## Fixed
24+
25+- **`plan update` without a status** (#91): the model sent the same call about 30 times, trying to edit the tasks. The answer now quotes the call, names the missing field, says that `tasks` is not read by `update`, and gives a call that works.
26+- **`install.sh`** removes the old binary before copying the new one. macOS could kill a binary that was overwritten in place.
27+
28+## Demos
29+
30+- `declared-tools` (an "ops desk" with four declared tools);
31+- `programming-answers` and `programming-answers-2` (mini-tutorials on Mellum2.1 Thinking).
32+
33+## Docs
34+
35+- **New how-to guides** (EN + FR): Declare a custom tool, Save an answer.
36+- **New explanation** (EN + FR): Declared tools.
new file mode 100644
@@ -0,0 +1,36 @@
1+# mini-me v0.6.0
2+
3+*[Version française](v0.6.0.fr.md)*
4+
5+Your own tools declared in the agent file, and answers saved to a file without being written again.
6+
7+## New
8+
9+### Declared tools (#88)
10+
11+- **A `tools:` block** declares your own tools. Each one is a command, run as an argument list and **never through a shell**.
12+- **Parameters** can be `string`, `enum`, `boolean`, `integer` or `number`. `"3"` and `"true"` are accepted for numbers and booleans.
13+- **Invalid calls** are answered in text, quoting the call back.
14+- **Consent follows `yolo`.**
15+- **Names**: built-in tool names are reserved, even when the tool is turned off. A clash with an MCP tool is a warning.
16+- Measured with Mellum2 12B and Qwen3.5 4B: 30/30 right calls each, all in a single call.
17+
18+### Save an answer
19+
20+- **`/save <file> [n]`** writes the last answer (or the n-th from the end) to a new file. It never overwrites an existing file.
21+- **`save_answer`**: the model saves an answer it already gave by sending only the file name, not the text again. It is turned on with `saveAnswerTool`, off by default.
22+
23+## Fixed
24+
25+- **`plan update` without a status** (#91): the model sent the same call about 30 times, trying to edit the tasks. The answer now quotes the call, names the missing field, says that `tasks` is not read by `update`, and gives a call that works.
26+- **`install.sh`** removes the old binary before copying the new one. macOS could kill a binary that was overwritten in place.
27+
28+## Demos
29+
30+- `declared-tools` (an "ops desk" with four declared tools);
31+- `programming-answers` and `programming-answers-2` (mini-tutorials on Mellum2.1 Thinking).
32+
33+## Docs
34+
35+- **New how-to guides** (EN + FR): Declare a custom tool, Save an answer.
36+- **New explanation** (EN + FR): Declared tools.
added release-notes/v0.6.0.fr.md +36 -0
new file mode 100644
@@ -0,0 +1,36 @@
1+# mini-me v0.6.0
2+
3+*[English version](v0.6.0.en.md)*
4+
5+Vos propres outils déclarés dans le fichier de l'agent, et des réponses enregistrées dans un fichier sans être réécrites.
6+
7+## Nouveautés
8+
9+### Outils déclarés (#88)
10+
11+- **Un bloc `tools:`** déclare vos propres outils. Chacun est une commande, lancée comme une liste d'arguments et **jamais à travers un shell**.
12+- **Les paramètres** peuvent être `string`, `enum`, `boolean`, `integer` ou `number`. `"3"` et `"true"` sont acceptés pour les nombres et les booléens.
13+- **Les appels invalides** reçoivent une réponse en texte qui cite l'appel.
14+- **Le consentement suit `yolo`.**
15+- **Noms** : les noms des outils intégrés sont réservés, même quand l'outil est désactivé. Un conflit avec un outil MCP donne un avertissement.
16+- Mesuré avec Mellum2 12B et Qwen3.5 4B : 30 appels justes sur 30 pour chacun, tous en un seul appel.
17+
18+### Enregistrer une réponse
19+
20+- **`/save <fichier> [n]`** écrit la dernière réponse (ou la n-ième en partant de la fin) dans un nouveau fichier. Il n'écrase jamais un fichier existant.
21+- **`save_answer`** : le modèle enregistre une réponse qu'il a déjà donnée en envoyant seulement le nom du fichier, pas le texte une seconde fois. Il s'active avec `saveAnswerTool`, désactivé par défaut.
22+
23+## Corrigé
24+
25+- **`plan update` sans statut** (#91) : le modèle envoyait le même appel une trentaine de fois en essayant de modifier les tâches. La réponse cite maintenant l'appel, nomme le champ manquant, dit que `tasks` n'est pas lu par `update`, et donne un appel qui marche.
26+- **`install.sh`** supprime l'ancien binaire avant de copier le nouveau. macOS pouvait tuer un binaire écrasé sur place.
27+
28+## Démos
29+
30+- `declared-tools` (un « guichet ops » avec quatre outils déclarés) ;
31+- `programming-answers` et `programming-answers-2` (des mini-tutoriels sur Mellum2.1 Thinking).
32+
33+## Documentation
34+
35+- **Nouveaux guides pratiques** (EN + FR) : Déclarer un outil personnalisé, Enregistrer une réponse.
36+- **Nouvelle explication** (EN + FR) : Les outils déclarés.
new file mode 100644
@@ -0,0 +1,36 @@
1+# mini-me v0.6.0
2+
3+*[English version](v0.6.0.en.md)*
4+
5+Vos propres outils déclarés dans le fichier de l'agent, et des réponses enregistrées dans un fichier sans être réécrites.
6+
7+## Nouveautés
8+
9+### Outils déclarés (#88)
10+
11+- **Un bloc `tools:`** déclare vos propres outils. Chacun est une commande, lancée comme une liste d'arguments et **jamais à travers un shell**.
12+- **Les paramètres** peuvent être `string`, `enum`, `boolean`, `integer` ou `number`. `"3"` et `"true"` sont acceptés pour les nombres et les booléens.
13+- **Les appels invalides** reçoivent une réponse en texte qui cite l'appel.
14+- **Le consentement suit `yolo`.**
15+- **Noms** : les noms des outils intégrés sont réservés, même quand l'outil est désactivé. Un conflit avec un outil MCP donne un avertissement.
16+- Mesuré avec Mellum2 12B et Qwen3.5 4B : 30 appels justes sur 30 pour chacun, tous en un seul appel.
17+
18+### Enregistrer une réponse
19+
20+- **`/save <fichier> [n]`** écrit la dernière réponse (ou la n-ième en partant de la fin) dans un nouveau fichier. Il n'écrase jamais un fichier existant.
21+- **`save_answer`** : le modèle enregistre une réponse qu'il a déjà donnée en envoyant seulement le nom du fichier, pas le texte une seconde fois. Il s'active avec `saveAnswerTool`, désactivé par défaut.
22+
23+## Corrigé
24+
25+- **`plan update` sans statut** (#91) : le modèle envoyait le même appel une trentaine de fois en essayant de modifier les tâches. La réponse cite maintenant l'appel, nomme le champ manquant, dit que `tasks` n'est pas lu par `update`, et donne un appel qui marche.
26+- **`install.sh`** supprime l'ancien binaire avant de copier le nouveau. macOS pouvait tuer un binaire écrasé sur place.
27+
28+## Démos
29+
30+- `declared-tools` (un « guichet ops » avec quatre outils déclarés) ;
31+- `programming-answers` et `programming-answers-2` (des mini-tutoriels sur Mellum2.1 Thinking).
32+
33+## Documentation
34+
35+- **Nouveaux guides pratiques** (EN + FR) : Déclarer un outil personnalisé, Enregistrer une réponse.
36+- **Nouvelle explication** (EN + FR) : Les outils déclarés.