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

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

Download ZIP

Recall last question #35

Merged@k33g wants to merge feature/last-question into main
Merged as 5340d5958a43.
81 files changed
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
Large diff collapsed to keep the page fast — load it when you need it.
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
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/README.md +33 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.0.0.en.md +61 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.0.0.fr.md +61 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.0.1.en.md +16 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.0.1.fr.md +16 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.0.2.en.md +37 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.0.2.fr.md +37 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.0.3.en.md +51 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.0.3.fr.md +51 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.0.4.en.md +9 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.0.4.fr.md +9 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.0.en.md +13 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.0.fr.md +13 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.1.en.md +19 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.1.fr.md +19 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.2.en.md +16 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.2.fr.md +16 -0
Large diff collapsed to keep the page fast — load it when you need it.
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
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.4.en.md +11 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.4.fr.md +11 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.5.en.md +7 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.5.fr.md +7 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.6.en.md +14 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.6.fr.md +14 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.7.en.md +18 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.7.fr.md +18 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.8.en.md +14 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.8.fr.md +14 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.9.en.md +7 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.1.9.fr.md +7 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.0.en.md +18 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.0.fr.md +18 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.1.en.md +13 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.1.fr.md +13 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.2.en.md +11 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.2.fr.md +11 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.3.en.md +17 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.3.fr.md +17 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.4.en.md +15 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.4.fr.md +15 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.5.en.md +13 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.5.fr.md +13 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.6.en.md +38 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.2.6.fr.md +38 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.3.0.en.md +61 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.3.0.fr.md +61 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.4.0.en.md +36 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.4.0.fr.md +36 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.5.0.en.md +49 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.5.0.fr.md +49 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.6.0.en.md +36 -0
Large diff collapsed to keep the page fast — load it when you need it.
added release-notes/v0.6.0.fr.md +36 -0
Large diff collapsed to keep the page fast — load it when you need it.