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

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

Download ZIP

Merge fix/plan-mode into main

rickub committed 2026-10-21T05:42:04Z Browse files
f0565c3 parents: 946a384 7087d46
added .tickets/issues/0108-plan-store-shared-by-every-session.yaml +106 -0
new file mode 100644
@@ -0,0 +1,106 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 108
3+title: "plan: one store for the whole process — every ACP session sees the same plans"
4+state: closed
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-21T08:00:00.000Z
9+updatedAt: 2026-10-21T10:00:00.000Z
10+labels:
11+ - kind::bug
12+milestone: v0.9.0
13+epic: Builtin tools
14+related:
15+ - 109
16+ - 110
17+body: |
18+ ## The problem
19+
20+ The `plan` tool keeps its plans in memory, in ONE store for the whole mm
21+ process: `internal/tools/plan.go`, `getSessionID()` returns the constant
22+ "current-session" ("v1: use a single session for simplicity"). Every plan
23+ lives under that one key.
24+
25+ In the line mode and the window this goes unnoticed: one process, one
26+ conversation at a time. Over ACP it does not hold: one `mm -acp` process
27+ serves every thread the editor opens (Zed: each thread is a session/new),
28+ and they all share that one store. Concretely, with two threads open in
29+ Zed:
30+
31+ - thread A asks "do these 3 things…": the model creates `user-request-1`;
32+ - thread B, about something else, asks for a plan too: its unnamed plan
33+ becomes `user-request-2` (the name counts A's plan), and
34+ `plan list` in B answers with BOTH plans;
35+ - the model in B can read, `update`, `next` or `delete` A's plan — a small
36+ model told "Found 2 plan(s)" will happily work on the wrong one;
37+ - a `create` with a name A already used fails with
38+ `plan 'x' already exists`, for a thread that never created it.
39+
40+ Nothing tells the user or the model that the plans come from another
41+ conversation. It is also why plans cannot be written to the session file
42+ (the code's own "v2" note): they are not attached to a session at all.
43+
44+ ## What fixing it solves
45+
46+ - Each conversation has its own plans: one Zed thread can no longer see,
47+ change or delete another thread's plan, and plan names restart at
48+ `user-request-1` in each.
49+ - A prerequisite for the two follow-ups: /new clearing the right plans in
50+ every front end (ticket 109), and later keeping plans in the session file
51+ so /resume and session/load bring them back.
52+
53+ ## Direction (to confirm)
54+
55+ Key the store by the conversation the call belongs to. The tool does not
56+ know it today: the session id is not passed to tools (the same gap
57+ search_memory worked around in ticket 48). Options: a session id in the
58+ tool's context, set by each front end next to ui.SetActive; or one plan
59+ store per front-end session, handed to the tool through the active sink.
60+tasks:
61+ - id: 1
62+ title: "Choose how a tool call learns its conversation (context value vs the active sink) — the same gap as ticket 48's search_memory"
63+ state: closed
64+ priority: none
65+ author:
66+ name: k33g
67+ email: ph.charriere@gmail.com
68+ createdAt: 2026-10-21T08:00:00.000Z
69+ updatedAt: 2026-10-21T08:00:00.000Z
70+ - id: 2
71+ title: "Key the plan store by that conversation; plans of one ACP session invisible to another — with a test of two sessions over the wire"
72+ state: closed
73+ priority: none
74+ author:
75+ name: k33g
76+ email: ph.charriere@gmail.com
77+ createdAt: 2026-10-21T08:00:00.000Z
78+ updatedAt: 2026-10-21T08:00:00.000Z
79+ - id: 3
80+ title: "Docs EN+FR: reference/tools.md plan › Storage row"
81+ state: closed
82+ priority: none
83+ author:
84+ name: k33g
85+ email: ph.charriere@gmail.com
86+ createdAt: 2026-10-21T08:00:00.000Z
87+ updatedAt: 2026-10-21T08:00:00.000Z
88+comments:
89+ - id: 1
90+ author:
91+ name: k33g
92+ email: ph.charriere@gmail.com
93+ createdAt: 2026-10-21T10:00:00.000Z
94+ body: |
95+ Fixed on fix/plan-mode (user's request: "fixe les 3 tickets").
96+ - internal/ui: optional interface Conversation { ConversationID() }, and
97+ ui.ConversationID() — the active sink's, "" for the front ends that hold
98+ one conversation (line mode, window).
99+ - internal/acp/sink.go: ConversationID() = the ACP session id.
100+ - internal/tools/plan.go: getSessionID() = ui.ConversationID() (was the
101+ constant "current-session"); ClearPlans(conversation) replaces
102+ ClearAllPlans().
103+ - Tests: tools/plan_conversation_test.go TestPlansArePerConversation (two
104+ threads: each first unnamed plan is user-request-1, A never lists or
105+ reads B's plan, clearing A keeps B's).
106+ - Docs EN+FR: reference/tools.md plan › Storage.
new file mode 100644
@@ -0,0 +1,106 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 108
3+title: "plan: one store for the whole process — every ACP session sees the same plans"
4+state: closed
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-21T08:00:00.000Z
9+updatedAt: 2026-10-21T10:00:00.000Z
10+labels:
11+ - kind::bug
12+milestone: v0.9.0
13+epic: Builtin tools
14+related:
15+ - 109
16+ - 110
17+body: |
18+ ## The problem
19+
20+ The `plan` tool keeps its plans in memory, in ONE store for the whole mm
21+ process: `internal/tools/plan.go`, `getSessionID()` returns the constant
22+ "current-session" ("v1: use a single session for simplicity"). Every plan
23+ lives under that one key.
24+
25+ In the line mode and the window this goes unnoticed: one process, one
26+ conversation at a time. Over ACP it does not hold: one `mm -acp` process
27+ serves every thread the editor opens (Zed: each thread is a session/new),
28+ and they all share that one store. Concretely, with two threads open in
29+ Zed:
30+
31+ - thread A asks "do these 3 things…": the model creates `user-request-1`;
32+ - thread B, about something else, asks for a plan too: its unnamed plan
33+ becomes `user-request-2` (the name counts A's plan), and
34+ `plan list` in B answers with BOTH plans;
35+ - the model in B can read, `update`, `next` or `delete` A's plan — a small
36+ model told "Found 2 plan(s)" will happily work on the wrong one;
37+ - a `create` with a name A already used fails with
38+ `plan 'x' already exists`, for a thread that never created it.
39+
40+ Nothing tells the user or the model that the plans come from another
41+ conversation. It is also why plans cannot be written to the session file
42+ (the code's own "v2" note): they are not attached to a session at all.
43+
44+ ## What fixing it solves
45+
46+ - Each conversation has its own plans: one Zed thread can no longer see,
47+ change or delete another thread's plan, and plan names restart at
48+ `user-request-1` in each.
49+ - A prerequisite for the two follow-ups: /new clearing the right plans in
50+ every front end (ticket 109), and later keeping plans in the session file
51+ so /resume and session/load bring them back.
52+
53+ ## Direction (to confirm)
54+
55+ Key the store by the conversation the call belongs to. The tool does not
56+ know it today: the session id is not passed to tools (the same gap
57+ search_memory worked around in ticket 48). Options: a session id in the
58+ tool's context, set by each front end next to ui.SetActive; or one plan
59+ store per front-end session, handed to the tool through the active sink.
60+tasks:
61+ - id: 1
62+ title: "Choose how a tool call learns its conversation (context value vs the active sink) — the same gap as ticket 48's search_memory"
63+ state: closed
64+ priority: none
65+ author:
66+ name: k33g
67+ email: ph.charriere@gmail.com
68+ createdAt: 2026-10-21T08:00:00.000Z
69+ updatedAt: 2026-10-21T08:00:00.000Z
70+ - id: 2
71+ title: "Key the plan store by that conversation; plans of one ACP session invisible to another — with a test of two sessions over the wire"
72+ state: closed
73+ priority: none
74+ author:
75+ name: k33g
76+ email: ph.charriere@gmail.com
77+ createdAt: 2026-10-21T08:00:00.000Z
78+ updatedAt: 2026-10-21T08:00:00.000Z
79+ - id: 3
80+ title: "Docs EN+FR: reference/tools.md plan › Storage row"
81+ state: closed
82+ priority: none
83+ author:
84+ name: k33g
85+ email: ph.charriere@gmail.com
86+ createdAt: 2026-10-21T08:00:00.000Z
87+ updatedAt: 2026-10-21T08:00:00.000Z
88+comments:
89+ - id: 1
90+ author:
91+ name: k33g
92+ email: ph.charriere@gmail.com
93+ createdAt: 2026-10-21T10:00:00.000Z
94+ body: |
95+ Fixed on fix/plan-mode (user's request: "fixe les 3 tickets").
96+ - internal/ui: optional interface Conversation { ConversationID() }, and
97+ ui.ConversationID() — the active sink's, "" for the front ends that hold
98+ one conversation (line mode, window).
99+ - internal/acp/sink.go: ConversationID() = the ACP session id.
100+ - internal/tools/plan.go: getSessionID() = ui.ConversationID() (was the
101+ constant "current-session"); ClearPlans(conversation) replaces
102+ ClearAllPlans().
103+ - Tests: tools/plan_conversation_test.go TestPlansArePerConversation (two
104+ threads: each first unnamed plan is user-request-1, A never lists or
105+ reads B's plan, clearing A keeps B's).
106+ - Docs EN+FR: reference/tools.md plan › Storage.
added .tickets/issues/0109-new-does-not-clear-plans-in-window-and-acp.yaml +99 -0
new file mode 100644
@@ -0,0 +1,99 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 109
3+title: "/new clears the plans in the line mode only — not in the window, not over ACP"
4+state: closed
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-21T08:00:00.000Z
9+updatedAt: 2026-10-21T10:00:00.000Z
10+labels:
11+ - kind::bug
12+milestone: v0.9.0
13+epic: Builtin tools
14+related:
15+ - 108
16+ - 110
17+body: |
18+ ## The problem
19+
20+ `/new` means "a new session: forget the conversation". In the line mode it
21+ also forgets the plans: `internal/agent/agent.go` startNewSession calls
22+ `tools.ClearAllPlans()`. The two other front ends do not:
23+
24+ - the window: `internal/tui/commands.go` runner.newSession resets the
25+ history, the token count, the loop detector and the session file — not
26+ the plans;
27+ - ACP: `internal/acp/acp.go` front.startNewSession archives the
28+ conversation and resets the history — not the plans.
29+
30+ So after `/new` in the window or in an editor, the conversation is empty
31+ but the plan of the previous task is still there: the next `plan list`
32+ returns it, an unnamed `create` is called `user-request-2`, and a model that
33+ reads the old plan may go back to tasks of a conversation the user just
34+ closed. The same `/new` behaves differently depending on where it is typed
35+ — the line mode is right, the two others are not.
36+
37+ (Over ACP the call would today clear EVERY session's plans, since the store
38+ is shared — ticket 108. The two fixes go together.)
39+
40+ ## What fixing it solves
41+
42+ - `/new` forgets the plans in the three front ends, as it forgets the
43+ history: a new task starts with no leftover plan.
44+ - One rule for all front ends, as the shared command table promises.
45+
46+ ## Direction (to confirm)
47+
48+ Call the plan reset from the shared new-session path (or from each of the
49+ two missing ones), and — once ticket 108 is done — reset only the plans of
50+ the session being renewed. Decide too what /resume should do with the
51+ plans in memory (today: nothing, in every front end).
52+tasks:
53+ - id: 1
54+ title: "Clear the plans on /new in the window and over ACP (after or with ticket 108, so an ACP /new clears only its own session) — with tests"
55+ state: closed
56+ priority: none
57+ author:
58+ name: k33g
59+ email: ph.charriere@gmail.com
60+ createdAt: 2026-10-21T08:00:00.000Z
61+ updatedAt: 2026-10-21T08:00:00.000Z
62+ - id: 2
63+ title: "Decide and document what /resume and session/load do with the plans in memory"
64+ state: closed
65+ priority: none
66+ author:
67+ name: k33g
68+ email: ph.charriere@gmail.com
69+ createdAt: 2026-10-21T08:00:00.000Z
70+ updatedAt: 2026-10-21T08:00:00.000Z
71+ - id: 3
72+ title: "Docs EN+FR: reference/tools.md plan › Storage row; reference/slash-commands.md /new"
73+ state: closed
74+ priority: none
75+ author:
76+ name: k33g
77+ email: ph.charriere@gmail.com
78+ createdAt: 2026-10-21T08:00:00.000Z
79+ updatedAt: 2026-10-21T08:00:00.000Z
80+comments:
81+ - id: 1
82+ author:
83+ name: k33g
84+ email: ph.charriere@gmail.com
85+ createdAt: 2026-10-21T10:00:00.000Z
86+ body: |
87+ Fixed on fix/plan-mode. Decision (task 2): plans belong to a conversation
88+ and are not saved, so a conversation that changes starts with no plan:
89+ - line mode: /new (already) and /resume → tools.ClearPlans("");
90+ - window: runner.newSession and runner.resume → tools.ClearPlans("");
91+ - ACP: startNewSession → tools.ClearPlans(sid) (this thread only);
92+ session/load and session/resume (the shared restore) →
93+ tools.ClearPlans(id).
94+ - Tests: tui TestRunnerNewAndResumeClearThePlans (fails without the fix:
95+ checked by removing the two calls), acp
96+ TestNewOverACPClearsOnlyItsThreadsPlans (over the wire, two sessions),
97+ acp TestLoadSessionStartsWithNoPlan.
98+ - Docs EN+FR: reference/tools.md plan › Storage; slash-commands /new
99+ (terminal + ACP) and /resume.
new file mode 100644
@@ -0,0 +1,99 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 109
3+title: "/new clears the plans in the line mode only — not in the window, not over ACP"
4+state: closed
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-21T08:00:00.000Z
9+updatedAt: 2026-10-21T10:00:00.000Z
10+labels:
11+ - kind::bug
12+milestone: v0.9.0
13+epic: Builtin tools
14+related:
15+ - 108
16+ - 110
17+body: |
18+ ## The problem
19+
20+ `/new` means "a new session: forget the conversation". In the line mode it
21+ also forgets the plans: `internal/agent/agent.go` startNewSession calls
22+ `tools.ClearAllPlans()`. The two other front ends do not:
23+
24+ - the window: `internal/tui/commands.go` runner.newSession resets the
25+ history, the token count, the loop detector and the session file — not
26+ the plans;
27+ - ACP: `internal/acp/acp.go` front.startNewSession archives the
28+ conversation and resets the history — not the plans.
29+
30+ So after `/new` in the window or in an editor, the conversation is empty
31+ but the plan of the previous task is still there: the next `plan list`
32+ returns it, an unnamed `create` is called `user-request-2`, and a model that
33+ reads the old plan may go back to tasks of a conversation the user just
34+ closed. The same `/new` behaves differently depending on where it is typed
35+ — the line mode is right, the two others are not.
36+
37+ (Over ACP the call would today clear EVERY session's plans, since the store
38+ is shared — ticket 108. The two fixes go together.)
39+
40+ ## What fixing it solves
41+
42+ - `/new` forgets the plans in the three front ends, as it forgets the
43+ history: a new task starts with no leftover plan.
44+ - One rule for all front ends, as the shared command table promises.
45+
46+ ## Direction (to confirm)
47+
48+ Call the plan reset from the shared new-session path (or from each of the
49+ two missing ones), and — once ticket 108 is done — reset only the plans of
50+ the session being renewed. Decide too what /resume should do with the
51+ plans in memory (today: nothing, in every front end).
52+tasks:
53+ - id: 1
54+ title: "Clear the plans on /new in the window and over ACP (after or with ticket 108, so an ACP /new clears only its own session) — with tests"
55+ state: closed
56+ priority: none
57+ author:
58+ name: k33g
59+ email: ph.charriere@gmail.com
60+ createdAt: 2026-10-21T08:00:00.000Z
61+ updatedAt: 2026-10-21T08:00:00.000Z
62+ - id: 2
63+ title: "Decide and document what /resume and session/load do with the plans in memory"
64+ state: closed
65+ priority: none
66+ author:
67+ name: k33g
68+ email: ph.charriere@gmail.com
69+ createdAt: 2026-10-21T08:00:00.000Z
70+ updatedAt: 2026-10-21T08:00:00.000Z
71+ - id: 3
72+ title: "Docs EN+FR: reference/tools.md plan › Storage row; reference/slash-commands.md /new"
73+ state: closed
74+ priority: none
75+ author:
76+ name: k33g
77+ email: ph.charriere@gmail.com
78+ createdAt: 2026-10-21T08:00:00.000Z
79+ updatedAt: 2026-10-21T08:00:00.000Z
80+comments:
81+ - id: 1
82+ author:
83+ name: k33g
84+ email: ph.charriere@gmail.com
85+ createdAt: 2026-10-21T10:00:00.000Z
86+ body: |
87+ Fixed on fix/plan-mode. Decision (task 2): plans belong to a conversation
88+ and are not saved, so a conversation that changes starts with no plan:
89+ - line mode: /new (already) and /resume → tools.ClearPlans("");
90+ - window: runner.newSession and runner.resume → tools.ClearPlans("");
91+ - ACP: startNewSession → tools.ClearPlans(sid) (this thread only);
92+ session/load and session/resume (the shared restore) →
93+ tools.ClearPlans(id).
94+ - Tests: tui TestRunnerNewAndResumeClearThePlans (fails without the fix:
95+ checked by removing the two calls), acp
96+ TestNewOverACPClearsOnlyItsThreadsPlans (over the wire, two sessions),
97+ acp TestLoadSessionStartsWithNoPlan.
98+ - Docs EN+FR: reference/tools.md plan › Storage; slash-commands /new
99+ (terminal + ACP) and /resume.
added .tickets/issues/0110-plans-how-to-promises-progress-lines-mm-never-prints.yaml +134 -0
new file mode 100644
@@ -0,0 +1,134 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 110
3+title: "Plans how-to shows progress lines (📋 [2/3] …) that mm never prints"
4+state: closed
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-21T08:00:00.000Z
9+updatedAt: 2026-10-21T10:00:00.000Z
10+labels:
11+ - kind::bug
12+milestone: v0.9.0
13+epic: Documentation
14+related:
15+ - 108
16+ - 109
17+body: |
18+ ## The problem
19+
20+ docs/en/how-to/manage-plans-and-tasks.md (and its French twin) promises
21+ that mm shows where the agent is in a plan "after each response", with
22+ lines like:
23+
24+ 📋 [2/3] 🔄 Task 2: Add a license (in_progress)
25+ 📋 [3/3] ✅ All tasks complete!
26+
27+ and builds most of the page on them ("The 📋 line shows you exactly where
28+ the agent is in your plan", worked examples, a "status line format"
29+ section). No code prints those lines. What mm actually does
30+ (internal/tools/plan.go):
31+
32+ - line mode: it prints ONE line before each call, `📋 plan: <action>
33+ <name>` (e.g. `📋 plan: update user-request-1`), and nothing after: the
34+ result is not echoed;
35+ - the result — JSON text holding a `status` field such as
36+ `[2/3] 🔄 Task 2: Add a license (in_progress)` — goes to the MODEL. The
37+ tool's description asks the model to "include [it] in your response to
38+ the user"; a model that does shows a similar line, without the 📋, in its
39+ own words; one that does not shows nothing;
40+ - window and editor: the call appears as a tool call, its raw JSON result
41+ in the tool's frame.
42+
43+ The page also announces a `/plans` command ("not yet implemented in v1 —
44+ coming in v2") with a sample output, in a section titled "Plan Management
45+ Commands".
46+
47+ So a user who reads the guide expects a progress line mm never draws, and
48+ cannot tell whether a missing line means a bug, an old version, or a model
49+ that skipped it.
50+
51+ ## What fixing it solves
52+
53+ - The guide describes what the user will see, front end by front end, and
54+ says plainly that progress shown in the answer depends on the model.
55+ - If progress lines ARE wanted, that is a feature to decide (mm printing
56+ the status after each plan call, or after each answer), not something
57+ the docs pretend exists.
58+
59+ ## Direction (to decide)
60+
61+ Either rewrite the how-to (EN+FR) to match the code — the reference
62+ section written on 2026-10-21 already does —, or implement the promised
63+ line (e.g. print the `status` line after each plan action in the line mode
64+ and as a notice in the window) and then document it. The `/plans` section
65+ should go, or become a ticket of its own.
66+tasks:
67+ - id: 1
68+ title: "Decide: rewrite the how-to to match mm, or implement a progress line mm prints itself"
69+ state: closed
70+ priority: none
71+ author:
72+ name: k33g
73+ email: ph.charriere@gmail.com
74+ createdAt: 2026-10-21T08:00:00.000Z
75+ updatedAt: 2026-10-21T08:00:00.000Z
76+ - id: 2
77+ title: "Docs EN+FR: manage-plans-and-tasks.md matches what each front end shows; the /plans section removed or turned into a ticket"
78+ state: closed
79+ priority: none
80+ author:
81+ name: k33g
82+ email: ph.charriere@gmail.com
83+ createdAt: 2026-10-21T08:00:00.000Z
84+ updatedAt: 2026-10-21T08:00:00.000Z
85+comments:
86+ - id: 1
87+ author:
88+ name: k33g
89+ email: ph.charriere@gmail.com
90+ createdAt: 2026-10-21T10:00:00.000Z
91+ body: |
92+ Fixed on fix/plan-mode. Decision (task 1): implement the line, then
93+ document it.
94+ - internal/tools/plan.go progressLine: after every plan action whose result
95+ has a status (create — which now returns one —, update, next, status) mm
96+ shows `📋 <status line>`: printed in the line mode, a notice in the window
97+ (ui.Noticer); an editor shows the status in the tool call's result.
98+ - Making it visible exposed a pre-existing defect (live run, Qwen3.5 4B):
99+ the status line followed plan.Current, which only `next` moved, so after
100+ `update 1 done` it still showed task 1, and a later `next` put the
101+ finished task 1 back in progress. Fixed here, since the progress shown
102+ must be right: the line shows the first task in progress, else the first
103+ open one (`Plan finished: D done, F failed` when over with failures);
104+ `update … in_progress` makes the task current; `next` skips finished
105+ tasks. TestRunPlanAction encoded the old behaviour (next after update 1
106+ in_progress restarted task 1) — expectation corrected; quote escaping
107+ kept in its own test.
108+ - Cosmetic: the call title of an unnamed plan no longer ends with a space
109+ (planTitle).
110+ - Live (mm -p, Qwen3.5 4B): 📋 [1/2] … open → 📋 [2/2] … open → 📋 [2/2]
111+ All tasks complete!, files written.
112+ - Docs EN+FR: how-to manage-plans-and-tasks rewritten (450 → 64 lines: what
113+ each front end shows, the measured sample, steering in words, variants,
114+ troubleshooting; the /plans section removed — no such command), now in
115+ both indexes; reference/tools.md plan (create/update/next/status line/
116+ display rows); root README Plans line.
117+ - id: 2
118+ author:
119+ name: k33g
120+ email: ph.charriere@gmail.com
121+ createdAt: 2026-10-21T11:00:00.000Z
122+ body: |
123+ Follow-up, found by demos/plans (user's request: a plans demo): Qwen3.5
124+ 4B never passed `name` after create; every update failed ("no plan
125+ named ''") and the turn ended in runaway tool calls. An action without
126+ a name now works on the conversation's most recently created plan
127+ (PlanStore.LatestName, by creation order — CreatedAt has one-second
128+ precision); the field's description said "Defaults to 'user-request'",
129+ a name no plan has. Test TestPlanActionWithoutANameUsesTheLatestPlan.
130+ Docs EN+FR: reference plan › name. demos/plans measured: 4-step Go
131+ program 3/3 (go test passes), failing push 3/3 "Plan finished: 2 done,
132+ 1 failed" once the prompt forbids stand-ins (before: marked done 3/3; in
133+ the run inspected, a bare repo created in /tmp and force-pushed).
134+
new file mode 100644
@@ -0,0 +1,134 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 110
3+title: "Plans how-to shows progress lines (📋 [2/3] …) that mm never prints"
4+state: closed
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-21T08:00:00.000Z
9+updatedAt: 2026-10-21T10:00:00.000Z
10+labels:
11+ - kind::bug
12+milestone: v0.9.0
13+epic: Documentation
14+related:
15+ - 108
16+ - 109
17+body: |
18+ ## The problem
19+
20+ docs/en/how-to/manage-plans-and-tasks.md (and its French twin) promises
21+ that mm shows where the agent is in a plan "after each response", with
22+ lines like:
23+
24+ 📋 [2/3] 🔄 Task 2: Add a license (in_progress)
25+ 📋 [3/3] ✅ All tasks complete!
26+
27+ and builds most of the page on them ("The 📋 line shows you exactly where
28+ the agent is in your plan", worked examples, a "status line format"
29+ section). No code prints those lines. What mm actually does
30+ (internal/tools/plan.go):
31+
32+ - line mode: it prints ONE line before each call, `📋 plan: <action>
33+ <name>` (e.g. `📋 plan: update user-request-1`), and nothing after: the
34+ result is not echoed;
35+ - the result — JSON text holding a `status` field such as
36+ `[2/3] 🔄 Task 2: Add a license (in_progress)` — goes to the MODEL. The
37+ tool's description asks the model to "include [it] in your response to
38+ the user"; a model that does shows a similar line, without the 📋, in its
39+ own words; one that does not shows nothing;
40+ - window and editor: the call appears as a tool call, its raw JSON result
41+ in the tool's frame.
42+
43+ The page also announces a `/plans` command ("not yet implemented in v1 —
44+ coming in v2") with a sample output, in a section titled "Plan Management
45+ Commands".
46+
47+ So a user who reads the guide expects a progress line mm never draws, and
48+ cannot tell whether a missing line means a bug, an old version, or a model
49+ that skipped it.
50+
51+ ## What fixing it solves
52+
53+ - The guide describes what the user will see, front end by front end, and
54+ says plainly that progress shown in the answer depends on the model.
55+ - If progress lines ARE wanted, that is a feature to decide (mm printing
56+ the status after each plan call, or after each answer), not something
57+ the docs pretend exists.
58+
59+ ## Direction (to decide)
60+
61+ Either rewrite the how-to (EN+FR) to match the code — the reference
62+ section written on 2026-10-21 already does —, or implement the promised
63+ line (e.g. print the `status` line after each plan action in the line mode
64+ and as a notice in the window) and then document it. The `/plans` section
65+ should go, or become a ticket of its own.
66+tasks:
67+ - id: 1
68+ title: "Decide: rewrite the how-to to match mm, or implement a progress line mm prints itself"
69+ state: closed
70+ priority: none
71+ author:
72+ name: k33g
73+ email: ph.charriere@gmail.com
74+ createdAt: 2026-10-21T08:00:00.000Z
75+ updatedAt: 2026-10-21T08:00:00.000Z
76+ - id: 2
77+ title: "Docs EN+FR: manage-plans-and-tasks.md matches what each front end shows; the /plans section removed or turned into a ticket"
78+ state: closed
79+ priority: none
80+ author:
81+ name: k33g
82+ email: ph.charriere@gmail.com
83+ createdAt: 2026-10-21T08:00:00.000Z
84+ updatedAt: 2026-10-21T08:00:00.000Z
85+comments:
86+ - id: 1
87+ author:
88+ name: k33g
89+ email: ph.charriere@gmail.com
90+ createdAt: 2026-10-21T10:00:00.000Z
91+ body: |
92+ Fixed on fix/plan-mode. Decision (task 1): implement the line, then
93+ document it.
94+ - internal/tools/plan.go progressLine: after every plan action whose result
95+ has a status (create — which now returns one —, update, next, status) mm
96+ shows `📋 <status line>`: printed in the line mode, a notice in the window
97+ (ui.Noticer); an editor shows the status in the tool call's result.
98+ - Making it visible exposed a pre-existing defect (live run, Qwen3.5 4B):
99+ the status line followed plan.Current, which only `next` moved, so after
100+ `update 1 done` it still showed task 1, and a later `next` put the
101+ finished task 1 back in progress. Fixed here, since the progress shown
102+ must be right: the line shows the first task in progress, else the first
103+ open one (`Plan finished: D done, F failed` when over with failures);
104+ `update … in_progress` makes the task current; `next` skips finished
105+ tasks. TestRunPlanAction encoded the old behaviour (next after update 1
106+ in_progress restarted task 1) — expectation corrected; quote escaping
107+ kept in its own test.
108+ - Cosmetic: the call title of an unnamed plan no longer ends with a space
109+ (planTitle).
110+ - Live (mm -p, Qwen3.5 4B): 📋 [1/2] … open → 📋 [2/2] … open → 📋 [2/2]
111+ All tasks complete!, files written.
112+ - Docs EN+FR: how-to manage-plans-and-tasks rewritten (450 → 64 lines: what
113+ each front end shows, the measured sample, steering in words, variants,
114+ troubleshooting; the /plans section removed — no such command), now in
115+ both indexes; reference/tools.md plan (create/update/next/status line/
116+ display rows); root README Plans line.
117+ - id: 2
118+ author:
119+ name: k33g
120+ email: ph.charriere@gmail.com
121+ createdAt: 2026-10-21T11:00:00.000Z
122+ body: |
123+ Follow-up, found by demos/plans (user's request: a plans demo): Qwen3.5
124+ 4B never passed `name` after create; every update failed ("no plan
125+ named ''") and the turn ended in runaway tool calls. An action without
126+ a name now works on the conversation's most recently created plan
127+ (PlanStore.LatestName, by creation order — CreatedAt has one-second
128+ precision); the field's description said "Defaults to 'user-request'",
129+ a name no plan has. Test TestPlanActionWithoutANameUsesTheLatestPlan.
130+ Docs EN+FR: reference plan › name. demos/plans measured: 4-step Go
131+ program 3/3 (go test passes), failing push 3/3 "Plan finished: 2 done,
132+ 1 failed" once the prompt forbids stand-ins (before: marked done 3/3; in
133+ the run inspected, a bare repo created in /tmp and force-pushed).
134+
added .tickets/issues/0111-using-the-settings-guide-create-a-claude-skill.yaml +15 -0
new file mode 100644
@@ -0,0 +1,15 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 111
3+title: Using the settings guide create a Claude skill
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-21T04:27:19.433Z
9+updatedAt: 2026-10-21T04:41:59.368Z
10+labels: []
11+milestone: v1.0.0
12+body: |
13+ the Claude skill will produce a descriptive doc about the model and a config file (`agent.yaml`)
14+tasks: []
15+comments: []
new file mode 100644
@@ -0,0 +1,15 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 111
3+title: Using the settings guide create a Claude skill
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-21T04:27:19.433Z
9+updatedAt: 2026-10-21T04:41:59.368Z
10+labels: []
11+milestone: v1.0.0
12+body: |
13+ the Claude skill will produce a descriptive doc about the model and a config file (`agent.yaml`)
14+tasks: []
15+comments: []
added .tickets/issues/0112-add-a-help-window-in-tui-mode-for-the-ui-shortcu.yaml +15 -0
new file mode 100644
@@ -0,0 +1,15 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 112
3+title: Add a help window in TUI mode for the UI shortcuts
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-21T04:43:03.511Z
9+updatedAt: 2026-10-21T04:43:13.927Z
10+labels:
11+ - kind::feature
12+milestone: v0.9.0
13+body: ""
14+tasks: []
15+comments: []
new file mode 100644
@@ -0,0 +1,15 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 112
3+title: Add a help window in TUI mode for the UI shortcuts
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-10-21T04:43:03.511Z
9+updatedAt: 2026-10-21T04:43:13.927Z
10+labels:
11+ - kind::feature
12+milestone: v0.9.0
13+body: ""
14+tasks: []
15+comments: []
modified README.md +6 -4
@@ -65,7 +65,7 @@ Type a question at the `>` prompt. Each command the model runs is shown with its
6565 mm -tui
6666 ```
6767
68-The conversation above, a box to type in below. **Enter** sends, **Ctrl-J** adds a line, **Esc** cancels a turn, **F1** lists the keys. With `yolo: false` in the YAML, the agent asks before running a command or writing a file — in the window, the line mode and an editor alike (`yolo: true`, the default, runs them unasked).
68+The conversation above, a box to type in below. **Enter** sends, **Ctrl-J** adds a line, **Esc** cancels a turn (or clears the box when nothing runs), **F1** lists the keys. With sessions on, **↑** in the box brings back your earlier questions, from this session and the project's previous ones. With `yolo: false` in the YAML, the agent asks before running a command or writing a file — in the window, the line mode and an editor alike (`yolo: true`, the default, runs them unasked).
6969
7070 ### 3. Inside an editor (ACP)
7171
@@ -96,7 +96,7 @@ Speaks the Agent Client Protocol on stdin/stdout. You never run this one by hand
9696 `AGENT_CONFIG` must be an **absolute** path: Zed starts `mm` in the project directory, and a relative path would not be found. `"type": "custom"` is required.
9797
9898 3. Open the Agent panel, pick **mini-me**, and talk to it. Every `bash`, `read_file`, `write_file` and `edit_file` call shows up as a tool call, and file changes appear as real diffs. With `yolo: false` in the YAML, Zed asks you to allow each call before it runs; the shipped files say `yolo: true`, and the calls run unasked.
99-4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu; `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu).
99+4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu (with `/memory` and `/docs` when you turn them on); `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu).
100100 5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there.
101101
102102 ![mm-acp.png](/bots-garden/mini-me/raw/main/assets/mm-acp.png)
@@ -114,11 +114,11 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
114114 - **Project instructions**: an `AGENTS.md` in the project directory is added to the system prompt of every conversation. See [Give the agent your project's instructions](docs/en/how-to/give-project-instructions.md).
115115 - **Answers as JSON**: declare the shape of the answer as Go types under `structuredOutput`, and `mm -p "…" | jq` works. See [Get answers as JSON](docs/en/how-to/get-json-answers.md).
116116 - **MCP servers**: their tools, wrapped as mm tools. See the [MCP tutorial](docs/en/tutorials/mcp-getting-started.md).
117-- **Sessions**: with `sessions.enabled: true`, `/sessions` lists the conversations of a project and `/resume` continues one; in an editor, its thread history lists them (`session/list`) and reopens one (`session/load`, `session/resume`). See [Keep sessions and resume them](docs/en/how-to/keep-sessions.md).
117+- **Sessions**: with `sessions.enabled: true`, `/sessions` lists the conversations of a project and `/resume` continues one; in the window, ↑ recalls your earlier questions to send them again; in an editor, its thread history lists them (`session/list`) and reopens one (`session/load`, `session/resume`). See [Keep sessions and resume them](docs/en/how-to/keep-sessions.md).
118118 - **Trace**: `AGENT_TRACE=true mm …` writes every request to the model and every response to `.mm/traces/`. See [See the JSON sent to the model](docs/en/how-to/inspect-model-traffic.md).
119119 - **Hooks**: your own commands — in any language — run before a question reaches the model, before and after each tool call, after the turn, and around each hand-off to another agent; they can deny a call, stop or rewrite a question, add context, mask what leaves for another agent, replace what comes back, or log. `displayHooks: true` shows each hook that ran and what it decided, after the answer (an editor shows each one as a tool call). See [Guard the agent with hooks](docs/en/how-to/guard-the-agent-with-hooks.md), [Control the hand-offs to other agents with hooks](docs/en/how-to/control-agent-calls-with-hooks.md), `demos/hooks/` and `demos/hooks-ask/`.
120120 - **Guard models**: have every question checked by guard models (IBM's Granite Guardian), one agent per risk, before your assistant answers — a `preGeneration` hook asks them without the conversation history, and the assistant refuses or answers according to their verdicts. See [Screen questions with guard models](docs/en/how-to/screen-questions-with-guard-models.md), `demos/guard/` and `demos/guards/`.
121-- **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan and show its progress after each answer. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md).
121+- **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan; mm shows its progress (`📋 [2/3] …`) after each plan action — per conversation, cleared by `/new` and `/resume`. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md).
122122 - **Save an answer**: `/save` writes the last answer to a Markdown file; with `saveAnswerTool: true`, the model does it itself when you ask in your own words (`save_answer`). Never overwrites a file. See [Save an answer to a file](docs/en/how-to/save-an-answer.md).
123123 - **Unload a model**: `/unload [model]` frees a model from the server's memory — in the terminal, the window and an editor —, and the `unload_model` tool lets the agent do it itself, e.g. on another agent's model once it has answered; llmman, Ollama and llama-server in router mode. `unloadModels: false` turns both off. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/unload-models/`.
124124 - **Search a documentation folder**: `/docs <words>` searches the text files of the folders named in `docSearch.dirs` (`.md`, `.adoc`, `.txt`… — only the extensions listed) and returns the best passages with their `path:line` link, ranked with BM25 — no index, no model (`docSearch.searchDocs`); the `search_docs` tool lets the model do it before it answers (`docSearch.autoSearchDocs`); both off by default. See [Search a documentation folder](docs/en/how-to/search-the-docs.md) and `demos/docs-search/`.
@@ -156,6 +156,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h
156156 | `demos/guard/` | one guard model (Granite Guardian 3.2 3B-A800M on llmman) answering `Yes`/`No` to "is this question harmful?"; why its `system:` line is a risk name, not a prompt; custom risks |
157157 | `demos/guards/` | an assistant (Gemma 4 E4B) protected by seven guard agents, one per risk: a Node.js `preGeneration` hook asks them in parallel without the history, the assistant refuses or answers according to their verdicts; `displayHooks: true`; measured results |
158158 | `demos/unload-models/` | a coordinator (Qwen3.5 9B) hands programming questions to an expert (Qwen3.5 4B) with `ask_agent`, a `preAsk` hook unloads the coordinator's own model before the expert starts and a `postAsk` hook unloads the expert's once it has answered — the two models are never loaded together; `/unload` and `unload_model` on request; llmman, measured results |
159+| `demos/plans/` | the `plan` tool at work: Planner (Qwen3.5 4B on llmman) builds a tiny Go program in four steps — module, code, test, README — while mm shows `📋 [C/N] …` after each plan action; a failing variant ends on `Plan finished: 2 done, 1 failed`; measured results |
159160 | `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results |
160161 | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find |
161162 | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results |
@@ -163,6 +164,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h
163164 ## Learn more
164165
165166 - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.
167+- [Release notes](release-notes/README.md) — what changed in each version, in English and French.
166168 - [`guides/`](guides/) — practical guides; for now, the agent settings for small models, in [English](guides/agent-settings-for-small-models.md) and [French](guides/reglages-agents-petits-modeles.md): timeouts, `maxTurns`, `maxOutput`, `agentsMd.maxChars`, context compression, sampling — what each one does, its default, and how to set it for a small local model.
167169 - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.
168170 - `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md).
@@ -65,7 +65,7 @@ Type a question at the `>` prompt. Each command the model runs is shown with its
65 mm -tui65 mm -tui
66 ```66 ```
67 67
68-The conversation above, a box to type in below. **Enter** sends, **Ctrl-J** adds a line, **Esc** cancels a turn, **F1** lists the keys. With `yolo: false` in the YAML, the agent asks before running a command or writing a file — in the window, the line mode and an editor alike (`yolo: true`, the default, runs them unasked).68+The conversation above, a box to type in below. **Enter** sends, **Ctrl-J** adds a line, **Esc** cancels a turn (or clears the box when nothing runs), **F1** lists the keys. With sessions on, **↑** in the box brings back your earlier questions, from this session and the project's previous ones. With `yolo: false` in the YAML, the agent asks before running a command or writing a file — in the window, the line mode and an editor alike (`yolo: true`, the default, runs them unasked).
69 69
70 ### 3. Inside an editor (ACP)70 ### 3. Inside an editor (ACP)
71 71
@@ -96,7 +96,7 @@ Speaks the Agent Client Protocol on stdin/stdout. You never run this one by hand
96 `AGENT_CONFIG` must be an **absolute** path: Zed starts `mm` in the project directory, and a relative path would not be found. `"type": "custom"` is required.96 `AGENT_CONFIG` must be an **absolute** path: Zed starts `mm` in the project directory, and a relative path would not be found. `"type": "custom"` is required.
97 97
98 3. Open the Agent panel, pick **mini-me**, and talk to it. Every `bash`, `read_file`, `write_file` and `edit_file` call shows up as a tool call, and file changes appear as real diffs. With `yolo: false` in the YAML, Zed asks you to allow each call before it runs; the shipped files say `yolo: true`, and the calls run unasked.98 3. Open the Agent panel, pick **mini-me**, and talk to it. Every `bash`, `read_file`, `write_file` and `edit_file` call shows up as a tool call, and file changes appear as real diffs. With `yolo: false` in the YAML, Zed asks you to allow each call before it runs; the shipped files say `yolo: true`, and the calls run unasked.
99-4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu; `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu).99+4. Handy things: **Escape** cancels a turn; `/new` and `/compact` are in the slash-command menu (with `/memory` and `/docs` when you turn them on); `@file` attaches a file for the model to read; with `sessions.enabled: true` in the YAML, **Reload Agent** brings the whole conversation back, and the Agent panel's thread history lists the project's past conversations so you can reopen one (it is not in the `/` menu).
100 5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there.100 5. When something looks wrong, open `dev: open acp logs`: the banner, warnings and the agent's `[acp] …` trail go there.
101 101
102 ![mm-acp.png](/bots-garden/mini-me/raw/main/assets/mm-acp.png)102 ![mm-acp.png](/bots-garden/mini-me/raw/main/assets/mm-acp.png)
@@ -114,11 +114,11 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
114 - **Project instructions**: an `AGENTS.md` in the project directory is added to the system prompt of every conversation. See [Give the agent your project's instructions](docs/en/how-to/give-project-instructions.md).114 - **Project instructions**: an `AGENTS.md` in the project directory is added to the system prompt of every conversation. See [Give the agent your project's instructions](docs/en/how-to/give-project-instructions.md).
115 - **Answers as JSON**: declare the shape of the answer as Go types under `structuredOutput`, and `mm -p "…" | jq` works. See [Get answers as JSON](docs/en/how-to/get-json-answers.md).115 - **Answers as JSON**: declare the shape of the answer as Go types under `structuredOutput`, and `mm -p "…" | jq` works. See [Get answers as JSON](docs/en/how-to/get-json-answers.md).
116 - **MCP servers**: their tools, wrapped as mm tools. See the [MCP tutorial](docs/en/tutorials/mcp-getting-started.md).116 - **MCP servers**: their tools, wrapped as mm tools. See the [MCP tutorial](docs/en/tutorials/mcp-getting-started.md).
117-- **Sessions**: with `sessions.enabled: true`, `/sessions` lists the conversations of a project and `/resume` continues one; in an editor, its thread history lists them (`session/list`) and reopens one (`session/load`, `session/resume`). See [Keep sessions and resume them](docs/en/how-to/keep-sessions.md).117+- **Sessions**: with `sessions.enabled: true`, `/sessions` lists the conversations of a project and `/resume` continues one; in the window, ↑ recalls your earlier questions to send them again; in an editor, its thread history lists them (`session/list`) and reopens one (`session/load`, `session/resume`). See [Keep sessions and resume them](docs/en/how-to/keep-sessions.md).
118 - **Trace**: `AGENT_TRACE=true mm …` writes every request to the model and every response to `.mm/traces/`. See [See the JSON sent to the model](docs/en/how-to/inspect-model-traffic.md).118 - **Trace**: `AGENT_TRACE=true mm …` writes every request to the model and every response to `.mm/traces/`. See [See the JSON sent to the model](docs/en/how-to/inspect-model-traffic.md).
119 - **Hooks**: your own commands — in any language — run before a question reaches the model, before and after each tool call, after the turn, and around each hand-off to another agent; they can deny a call, stop or rewrite a question, add context, mask what leaves for another agent, replace what comes back, or log. `displayHooks: true` shows each hook that ran and what it decided, after the answer (an editor shows each one as a tool call). See [Guard the agent with hooks](docs/en/how-to/guard-the-agent-with-hooks.md), [Control the hand-offs to other agents with hooks](docs/en/how-to/control-agent-calls-with-hooks.md), `demos/hooks/` and `demos/hooks-ask/`.119 - **Hooks**: your own commands — in any language — run before a question reaches the model, before and after each tool call, after the turn, and around each hand-off to another agent; they can deny a call, stop or rewrite a question, add context, mask what leaves for another agent, replace what comes back, or log. `displayHooks: true` shows each hook that ran and what it decided, after the answer (an editor shows each one as a tool call). See [Guard the agent with hooks](docs/en/how-to/guard-the-agent-with-hooks.md), [Control the hand-offs to other agents with hooks](docs/en/how-to/control-agent-calls-with-hooks.md), `demos/hooks/` and `demos/hooks-ask/`.
120 - **Guard models**: have every question checked by guard models (IBM's Granite Guardian), one agent per risk, before your assistant answers — a `preGeneration` hook asks them without the conversation history, and the assistant refuses or answers according to their verdicts. See [Screen questions with guard models](docs/en/how-to/screen-questions-with-guard-models.md), `demos/guard/` and `demos/guards/`.120 - **Guard models**: have every question checked by guard models (IBM's Granite Guardian), one agent per risk, before your assistant answers — a `preGeneration` hook asks them without the conversation history, and the assistant refuses or answers according to their verdicts. See [Screen questions with guard models](docs/en/how-to/screen-questions-with-guard-models.md), `demos/guard/` and `demos/guards/`.
121-- **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan and show its progress after each answer. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md).121+- **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan; mm shows its progress (`📋 [2/3] …`) after each plan action — per conversation, cleared by `/new` and `/resume`. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md).
122 - **Save an answer**: `/save` writes the last answer to a Markdown file; with `saveAnswerTool: true`, the model does it itself when you ask in your own words (`save_answer`). Never overwrites a file. See [Save an answer to a file](docs/en/how-to/save-an-answer.md).122 - **Save an answer**: `/save` writes the last answer to a Markdown file; with `saveAnswerTool: true`, the model does it itself when you ask in your own words (`save_answer`). Never overwrites a file. See [Save an answer to a file](docs/en/how-to/save-an-answer.md).
123 - **Unload a model**: `/unload [model]` frees a model from the server's memory — in the terminal, the window and an editor —, and the `unload_model` tool lets the agent do it itself, e.g. on another agent's model once it has answered; llmman, Ollama and llama-server in router mode. `unloadModels: false` turns both off. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/unload-models/`.123 - **Unload a model**: `/unload [model]` frees a model from the server's memory — in the terminal, the window and an editor —, and the `unload_model` tool lets the agent do it itself, e.g. on another agent's model once it has answered; llmman, Ollama and llama-server in router mode. `unloadModels: false` turns both off. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/unload-models/`.
124 - **Search a documentation folder**: `/docs <words>` searches the text files of the folders named in `docSearch.dirs` (`.md`, `.adoc`, `.txt`… — only the extensions listed) and returns the best passages with their `path:line` link, ranked with BM25 — no index, no model (`docSearch.searchDocs`); the `search_docs` tool lets the model do it before it answers (`docSearch.autoSearchDocs`); both off by default. See [Search a documentation folder](docs/en/how-to/search-the-docs.md) and `demos/docs-search/`.124 - **Search a documentation folder**: `/docs <words>` searches the text files of the folders named in `docSearch.dirs` (`.md`, `.adoc`, `.txt`… — only the extensions listed) and returns the best passages with their `path:line` link, ranked with BM25 — no index, no model (`docSearch.searchDocs`); the `search_docs` tool lets the model do it before it answers (`docSearch.autoSearchDocs`); both off by default. See [Search a documentation folder](docs/en/how-to/search-the-docs.md) and `demos/docs-search/`.
@@ -156,6 +156,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h
156 | `demos/guard/` | one guard model (Granite Guardian 3.2 3B-A800M on llmman) answering `Yes`/`No` to "is this question harmful?"; why its `system:` line is a risk name, not a prompt; custom risks |156 | `demos/guard/` | one guard model (Granite Guardian 3.2 3B-A800M on llmman) answering `Yes`/`No` to "is this question harmful?"; why its `system:` line is a risk name, not a prompt; custom risks |
157 | `demos/guards/` | an assistant (Gemma 4 E4B) protected by seven guard agents, one per risk: a Node.js `preGeneration` hook asks them in parallel without the history, the assistant refuses or answers according to their verdicts; `displayHooks: true`; measured results |157 | `demos/guards/` | an assistant (Gemma 4 E4B) protected by seven guard agents, one per risk: a Node.js `preGeneration` hook asks them in parallel without the history, the assistant refuses or answers according to their verdicts; `displayHooks: true`; measured results |
158 | `demos/unload-models/` | a coordinator (Qwen3.5 9B) hands programming questions to an expert (Qwen3.5 4B) with `ask_agent`, a `preAsk` hook unloads the coordinator's own model before the expert starts and a `postAsk` hook unloads the expert's once it has answered — the two models are never loaded together; `/unload` and `unload_model` on request; llmman, measured results |158 | `demos/unload-models/` | a coordinator (Qwen3.5 9B) hands programming questions to an expert (Qwen3.5 4B) with `ask_agent`, a `preAsk` hook unloads the coordinator's own model before the expert starts and a `postAsk` hook unloads the expert's once it has answered — the two models are never loaded together; `/unload` and `unload_model` on request; llmman, measured results |
159+| `demos/plans/` | the `plan` tool at work: Planner (Qwen3.5 4B on llmman) builds a tiny Go program in four steps — module, code, test, README — while mm shows `📋 [C/N] …` after each plan action; a failing variant ends on `Plan finished: 2 done, 1 failed`; measured results |
159 | `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results |160 | `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results |
160 | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find |161 | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find |
161 | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results |162 | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results |
@@ -163,6 +164,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h
163 ## Learn more164 ## Learn more
164 165
165 - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.166 - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.
167+- [Release notes](release-notes/README.md) — what changed in each version, in English and French.
166 - [`guides/`](guides/) — practical guides; for now, the agent settings for small models, in [English](guides/agent-settings-for-small-models.md) and [French](guides/reglages-agents-petits-modeles.md): timeouts, `maxTurns`, `maxOutput`, `agentsMd.maxChars`, context compression, sampling — what each one does, its default, and how to set it for a small local model.168 - [`guides/`](guides/) — practical guides; for now, the agent settings for small models, in [English](guides/agent-settings-for-small-models.md) and [French](guides/reglages-agents-petits-modeles.md): timeouts, `maxTurns`, `maxOutput`, `agentsMd.maxChars`, context compression, sampling — what each one does, its default, and how to set it for a small local model.
167 - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.169 - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.
168 - `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md).170 - `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md).
added about_tools_test.go +21 -0
new file mode 100644
@@ -0,0 +1,21 @@
1+package main
2+
3+import (
4+ "strings"
5+ "testing"
6+
7+ "mm/internal/config"
8+ "mm/internal/version"
9+)
10+
11+// --about lists the built-in tools by hand; every name the config reserves
12+// must be in it, so a new built-in tool cannot be forgotten there again (the
13+// list stopped at lsp for seven releases).
14+func TestAboutListsEveryBuiltinTool(t *testing.T) {
15+ about := version.About()
16+ for _, name := range config.BuiltinToolNames {
17+ if !strings.Contains(about, name) {
18+ t.Errorf("mm --about does not list the built-in tool %q", name)
19+ }
20+ }
21+}
new file mode 100644
@@ -0,0 +1,21 @@
1+package main
2+
3+import (
4+ "strings"
5+ "testing"
6+
7+ "mm/internal/config"
8+ "mm/internal/version"
9+)
10+
11+// --about lists the built-in tools by hand; every name the config reserves
12+// must be in it, so a new built-in tool cannot be forgotten there again (the
13+// list stopped at lsp for seven releases).
14+func TestAboutListsEveryBuiltinTool(t *testing.T) {
15+ about := version.About()
16+ for _, name := range config.BuiltinToolNames {
17+ if !strings.Contains(about, name) {
18+ t.Errorf("mm --about does not list the built-in tool %q", name)
19+ }
20+ }
21+}
added demos/plans/README.en.md +71 -0
new file mode 100644
@@ -0,0 +1,71 @@
1+# mini-me demo: plans
2+
3+*[Version française](README.fr.md)*
4+
5+Planner builds a tiny Go program step by step — module, code, test, run the tests, README — and works through the steps with its `plan` tool. mm shows where the plan is after every plan action, whatever the model writes in its answer. The model is Qwen3.5 4B, served by [llmman](https://github.com/llmmanorg/llmman). Go must be on the `PATH`.
6+
7+## What is here
8+
9+| File | Role |
10+|---|---|
11+| `agent.yaml` | Planner: `planTool: true`, `bash` and the file tools; a system prompt that says how to use the plan (create, then `in_progress` / `done` / `failed` task by task) and to mark `failed`, not fake, a task that needs something missing. |
12+| `reset.sh` | Removes `./hello`, the project the agent builds, so the next run starts from nothing. |
13+
14+## Run the demo
15+
16+```bash
17+llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
18+cd demos/plans
19+./reset.sh
20+mm agent.yaml
21+```
22+
23+Then ask:
24+
25+```text
26+Do these 4 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. add a function Greet(name string) string and a test for it in main_test.go, 3. run go test and make sure it passes, 4. write a README.md that explains how to run the program.
27+```
28+
29+In the line mode the progress shows between the tool calls (output of a real run):
30+
31+```text
32+📋 plan: create
33+📋 [1/4] ⏳ Task 1: Create a Go module named hello with a main.go that prints Hello, mm! (open)
34+📋 plan: update
35+📋 [2/4] ⏳ Task 2: Add a function Greet(name string) string and a test for it in main_test.go (open)
36+📋 plan: update
37+📋 [3/4] ⏳ Task 3: Run go test and make sure it passes (open)
38+📋 plan: update
39+📋 [4/4] ⏳ Task 4: Write a README.md that explains how to run the program (open)
40+📋 plan: update
41+📋 [4/4] All tasks complete!
42+```
43+
44+In the window (`mm -tui agent.yaml`) each `📋 [C/N] …` line is a notice under the tool call; in an editor the status is inside the tool call's result.
45+
46+To see a failure, ask for something that cannot be done here:
47+
48+```text
49+Do these 3 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. run go build to check it compiles, 3. push the code to the git remote named origin.
50+```
51+
52+The plan ends on `📋 [3/3] Plan finished: 2 done, 1 failed`.
53+
54+## Measured results (2026-10-21, `mm -p`, llmman)
55+
56+Qwen3.5 4B Q4_K_M, `temperature: 0`, thinking off, this `agent.yaml`, a fresh copy of the demo for every run.
57+
58+| Request | Plan shown | Result | |
59+|---|---|---|---|
60+| the 4 steps above | `[1/4]` … `[4/4] All tasks complete!` | `go.mod`, `main.go`, `main_test.go`, `README.md`; `go test` passes (checked after the run) | 3/3 |
61+| the 3 steps with a push to `origin` | `[1/3]` … `[3/3] Plan finished: 2 done, 1 failed` | module built; the push marked `failed`, the answer says so | 3/3 |
62+
63+64 to 127 s for the 4 steps, 36 to 66 s for the failing case.
64+
65+## What this demo taught the plan tool
66+
67+- **A small model does not keep the plan's name.** In the first runs every `update` came without `name`, failed (`no plan named ''`), and the turn ended in runaway tool calls. Since then an action without a name works on the conversation's latest plan — for every agent, not only this one.
68+- **Progress is shown by mm.** The status line comes from the plan itself, after each action, so it is there even when the model writes nothing about it — and it follows the tasks however the model moves through them (here it marks each task `done` directly, without `in_progress`).
69+- **"Done" means the model says so.** Without the last paragraph of the prompt, asked to push to a remote that does not exist, the model marked the task done 3 times out of 3; in the run inspected, it had created a local bare repository in `/tmp` — outside the project folder — and force-pushed to it. With it, the task is marked `failed` (3/3). It still adds an example remote (`github.com/example/hello.git`) and tries to push before giving up; the push fails without credentials.
70+
71+See also: [How to manage plans and tasks](../../docs/en/how-to/manage-plans-and-tasks.md), [`plan`](../../docs/en/reference/tools.md#plan).
new file mode 100644
@@ -0,0 +1,71 @@
1+# mini-me demo: plans
2+
3+*[Version française](README.fr.md)*
4+
5+Planner builds a tiny Go program step by step — module, code, test, run the tests, README — and works through the steps with its `plan` tool. mm shows where the plan is after every plan action, whatever the model writes in its answer. The model is Qwen3.5 4B, served by [llmman](https://github.com/llmmanorg/llmman). Go must be on the `PATH`.
6+
7+## What is here
8+
9+| File | Role |
10+|---|---|
11+| `agent.yaml` | Planner: `planTool: true`, `bash` and the file tools; a system prompt that says how to use the plan (create, then `in_progress` / `done` / `failed` task by task) and to mark `failed`, not fake, a task that needs something missing. |
12+| `reset.sh` | Removes `./hello`, the project the agent builds, so the next run starts from nothing. |
13+
14+## Run the demo
15+
16+```bash
17+llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
18+cd demos/plans
19+./reset.sh
20+mm agent.yaml
21+```
22+
23+Then ask:
24+
25+```text
26+Do these 4 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. add a function Greet(name string) string and a test for it in main_test.go, 3. run go test and make sure it passes, 4. write a README.md that explains how to run the program.
27+```
28+
29+In the line mode the progress shows between the tool calls (output of a real run):
30+
31+```text
32+📋 plan: create
33+📋 [1/4] ⏳ Task 1: Create a Go module named hello with a main.go that prints Hello, mm! (open)
34+📋 plan: update
35+📋 [2/4] ⏳ Task 2: Add a function Greet(name string) string and a test for it in main_test.go (open)
36+📋 plan: update
37+📋 [3/4] ⏳ Task 3: Run go test and make sure it passes (open)
38+📋 plan: update
39+📋 [4/4] ⏳ Task 4: Write a README.md that explains how to run the program (open)
40+📋 plan: update
41+📋 [4/4] All tasks complete!
42+```
43+
44+In the window (`mm -tui agent.yaml`) each `📋 [C/N] …` line is a notice under the tool call; in an editor the status is inside the tool call's result.
45+
46+To see a failure, ask for something that cannot be done here:
47+
48+```text
49+Do these 3 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. run go build to check it compiles, 3. push the code to the git remote named origin.
50+```
51+
52+The plan ends on `📋 [3/3] Plan finished: 2 done, 1 failed`.
53+
54+## Measured results (2026-10-21, `mm -p`, llmman)
55+
56+Qwen3.5 4B Q4_K_M, `temperature: 0`, thinking off, this `agent.yaml`, a fresh copy of the demo for every run.
57+
58+| Request | Plan shown | Result | |
59+|---|---|---|---|
60+| the 4 steps above | `[1/4]` … `[4/4] All tasks complete!` | `go.mod`, `main.go`, `main_test.go`, `README.md`; `go test` passes (checked after the run) | 3/3 |
61+| the 3 steps with a push to `origin` | `[1/3]` … `[3/3] Plan finished: 2 done, 1 failed` | module built; the push marked `failed`, the answer says so | 3/3 |
62+
63+64 to 127 s for the 4 steps, 36 to 66 s for the failing case.
64+
65+## What this demo taught the plan tool
66+
67+- **A small model does not keep the plan's name.** In the first runs every `update` came without `name`, failed (`no plan named ''`), and the turn ended in runaway tool calls. Since then an action without a name works on the conversation's latest plan — for every agent, not only this one.
68+- **Progress is shown by mm.** The status line comes from the plan itself, after each action, so it is there even when the model writes nothing about it — and it follows the tasks however the model moves through them (here it marks each task `done` directly, without `in_progress`).
69+- **"Done" means the model says so.** Without the last paragraph of the prompt, asked to push to a remote that does not exist, the model marked the task done 3 times out of 3; in the run inspected, it had created a local bare repository in `/tmp` — outside the project folder — and force-pushed to it. With it, the task is marked `failed` (3/3). It still adds an example remote (`github.com/example/hello.git`) and tries to push before giving up; the push fails without credentials.
70+
71+See also: [How to manage plans and tasks](../../docs/en/how-to/manage-plans-and-tasks.md), [`plan`](../../docs/en/reference/tools.md#plan).
added demos/plans/README.fr.md +71 -0
new file mode 100644
@@ -0,0 +1,71 @@
1+# Démo mini-me : les plans
2+
3+*[English version](README.en.md)*
4+
5+Planner construit un petit programme Go étape par étape — module, code, test, lancement des tests, README — et suit les étapes avec son outil `plan`. mm affiche où en est le plan après chaque action sur le plan, quoi que le modèle écrive dans sa réponse. Le modèle est Qwen3.5 4B, servi par [llmman](https://github.com/llmmanorg/llmman). Go doit être dans le `PATH`.
6+
7+## Contenu
8+
9+| Fichier | Rôle |
10+|---|---|
11+| `agent.yaml` | Planner : `planTool: true`, `bash` et les outils de fichiers ; un prompt système qui dit comment utiliser le plan (créer, puis `in_progress` / `done` / `failed` tâche par tâche) et de marquer `failed`, sans tricher, une tâche qui demande quelque chose qui manque. |
12+| `reset.sh` | Supprime `./hello`, le projet que l'agent construit, pour que l'exécution suivante reparte de zéro. |
13+
14+## Lancer la démo
15+
16+```bash
17+llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
18+cd demos/plans
19+./reset.sh
20+mm agent.yaml
21+```
22+
23+Puis demandez :
24+
25+```text
26+Do these 4 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. add a function Greet(name string) string and a test for it in main_test.go, 3. run go test and make sure it passes, 4. write a README.md that explains how to run the program.
27+```
28+
29+En mode ligne, la progression s'affiche entre les appels d'outils (sortie d'une exécution réelle) :
30+
31+```text
32+📋 plan: create
33+📋 [1/4] ⏳ Task 1: Create a Go module named hello with a main.go that prints Hello, mm! (open)
34+📋 plan: update
35+📋 [2/4] ⏳ Task 2: Add a function Greet(name string) string and a test for it in main_test.go (open)
36+📋 plan: update
37+📋 [3/4] ⏳ Task 3: Run go test and make sure it passes (open)
38+📋 plan: update
39+📋 [4/4] ⏳ Task 4: Write a README.md that explains how to run the program (open)
40+📋 plan: update
41+📋 [4/4] All tasks complete!
42+```
43+
44+Dans la fenêtre (`mm -tui agent.yaml`), chaque ligne `📋 [C/N] …` est une notice sous l'appel d'outil ; dans un éditeur, le statut est dans le résultat de l'appel.
45+
46+Pour voir un échec, demandez quelque chose d'impossible ici :
47+
48+```text
49+Do these 3 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. run go build to check it compiles, 3. push the code to the git remote named origin.
50+```
51+
52+Le plan se termine sur `📋 [3/3] Plan finished: 2 done, 1 failed`.
53+
54+## Résultats mesurés (2026-10-21, `mm -p`, llmman)
55+
56+Qwen3.5 4B Q4_K_M, `temperature: 0`, réflexion désactivée, cet `agent.yaml`, une copie neuve de la démo à chaque exécution.
57+
58+| Demande | Plan affiché | Résultat | |
59+|---|---|---|---|
60+| les 4 étapes ci-dessus | `[1/4]` … `[4/4] All tasks complete!` | `go.mod`, `main.go`, `main_test.go`, `README.md` ; `go test` passe (vérifié après l'exécution) | 3/3 |
61+| les 3 étapes avec un push vers `origin` | `[1/3]` … `[3/3] Plan finished: 2 done, 1 failed` | module construit ; le push marqué `failed`, la réponse le dit | 3/3 |
62+
63+De 64 à 127 s pour les 4 étapes, de 36 à 66 s pour le cas d'échec.
64+
65+## Ce que cette démo a appris à l'outil plan
66+
67+- **Un petit modèle ne retient pas le nom du plan.** Lors des premières exécutions, chaque `update` arrivait sans `name`, échouait (`no plan named ''`), et le tour finissait en appels d'outils emballés. Depuis, une action sans nom porte sur le dernier plan de la conversation — pour tous les agents, pas seulement celui-ci.
68+- **La progression est affichée par mm.** La ligne de statut vient du plan lui-même, après chaque action : elle est là même quand le modèle n'en dit rien — et elle suit les tâches quelle que soit la façon dont le modèle les parcourt (ici il marque chaque tâche `done` directement, sans `in_progress`).
69+- **« Fait » veut dire « le modèle le dit ».** Sans le dernier paragraphe du prompt, invité à pousser vers un dépôt distant qui n'existe pas, le modèle a marqué la tâche faite 3 fois sur 3 ; dans l'exécution examinée, il avait créé un dépôt nu local dans `/tmp` — hors du dossier du projet — et y avait poussé avec `--force`. Avec ce paragraphe, la tâche est marquée `failed` (3/3). Il ajoute encore un remote d'exemple (`github.com/example/hello.git`) et tente de pousser avant d'abandonner ; le push échoue faute d'identifiants.
70+
71+Voir aussi : [Gérer des plans et des tâches](../../docs/fr/how-to/manage-plans-and-tasks.md), [`plan`](../../docs/fr/reference/tools.md#plan).
new file mode 100644
@@ -0,0 +1,71 @@
1+# Démo mini-me : les plans
2+
3+*[English version](README.en.md)*
4+
5+Planner construit un petit programme Go étape par étape — module, code, test, lancement des tests, README — et suit les étapes avec son outil `plan`. mm affiche où en est le plan après chaque action sur le plan, quoi que le modèle écrive dans sa réponse. Le modèle est Qwen3.5 4B, servi par [llmman](https://github.com/llmmanorg/llmman). Go doit être dans le `PATH`.
6+
7+## Contenu
8+
9+| Fichier | Rôle |
10+|---|---|
11+| `agent.yaml` | Planner : `planTool: true`, `bash` et les outils de fichiers ; un prompt système qui dit comment utiliser le plan (créer, puis `in_progress` / `done` / `failed` tâche par tâche) et de marquer `failed`, sans tricher, une tâche qui demande quelque chose qui manque. |
12+| `reset.sh` | Supprime `./hello`, le projet que l'agent construit, pour que l'exécution suivante reparte de zéro. |
13+
14+## Lancer la démo
15+
16+```bash
17+llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
18+cd demos/plans
19+./reset.sh
20+mm agent.yaml
21+```
22+
23+Puis demandez :
24+
25+```text
26+Do these 4 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. add a function Greet(name string) string and a test for it in main_test.go, 3. run go test and make sure it passes, 4. write a README.md that explains how to run the program.
27+```
28+
29+En mode ligne, la progression s'affiche entre les appels d'outils (sortie d'une exécution réelle) :
30+
31+```text
32+📋 plan: create
33+📋 [1/4] ⏳ Task 1: Create a Go module named hello with a main.go that prints Hello, mm! (open)
34+📋 plan: update
35+📋 [2/4] ⏳ Task 2: Add a function Greet(name string) string and a test for it in main_test.go (open)
36+📋 plan: update
37+📋 [3/4] ⏳ Task 3: Run go test and make sure it passes (open)
38+📋 plan: update
39+📋 [4/4] ⏳ Task 4: Write a README.md that explains how to run the program (open)
40+📋 plan: update
41+📋 [4/4] All tasks complete!
42+```
43+
44+Dans la fenêtre (`mm -tui agent.yaml`), chaque ligne `📋 [C/N] …` est une notice sous l'appel d'outil ; dans un éditeur, le statut est dans le résultat de l'appel.
45+
46+Pour voir un échec, demandez quelque chose d'impossible ici :
47+
48+```text
49+Do these 3 things in a new folder ./hello: 1. create a Go module named hello with a main.go that prints Hello, mm!, 2. run go build to check it compiles, 3. push the code to the git remote named origin.
50+```
51+
52+Le plan se termine sur `📋 [3/3] Plan finished: 2 done, 1 failed`.
53+
54+## Résultats mesurés (2026-10-21, `mm -p`, llmman)
55+
56+Qwen3.5 4B Q4_K_M, `temperature: 0`, réflexion désactivée, cet `agent.yaml`, une copie neuve de la démo à chaque exécution.
57+
58+| Demande | Plan affiché | Résultat | |
59+|---|---|---|---|
60+| les 4 étapes ci-dessus | `[1/4]` … `[4/4] All tasks complete!` | `go.mod`, `main.go`, `main_test.go`, `README.md` ; `go test` passe (vérifié après l'exécution) | 3/3 |
61+| les 3 étapes avec un push vers `origin` | `[1/3]` … `[3/3] Plan finished: 2 done, 1 failed` | module construit ; le push marqué `failed`, la réponse le dit | 3/3 |
62+
63+De 64 à 127 s pour les 4 étapes, de 36 à 66 s pour le cas d'échec.
64+
65+## Ce que cette démo a appris à l'outil plan
66+
67+- **Un petit modèle ne retient pas le nom du plan.** Lors des premières exécutions, chaque `update` arrivait sans `name`, échouait (`no plan named ''`), et le tour finissait en appels d'outils emballés. Depuis, une action sans nom porte sur le dernier plan de la conversation — pour tous les agents, pas seulement celui-ci.
68+- **La progression est affichée par mm.** La ligne de statut vient du plan lui-même, après chaque action : elle est là même quand le modèle n'en dit rien — et elle suit les tâches quelle que soit la façon dont le modèle les parcourt (ici il marque chaque tâche `done` directement, sans `in_progress`).
69+- **« Fait » veut dire « le modèle le dit ».** Sans le dernier paragraphe du prompt, invité à pousser vers un dépôt distant qui n'existe pas, le modèle a marqué la tâche faite 3 fois sur 3 ; dans l'exécution examinée, il avait créé un dépôt nu local dans `/tmp` — hors du dossier du projet — et y avait poussé avec `--force`. Avec ce paragraphe, la tâche est marquée `failed` (3/3). Il ajoute encore un remote d'exemple (`github.com/example/hello.git`) et tente de pousser avant d'abandonner ; le push échoue faute d'identifiants.
70+
71+Voir aussi : [Gérer des plans et des tâches](../../docs/fr/how-to/manage-plans-and-tasks.md), [`plan`](../../docs/fr/reference/tools.md#plan).
added demos/plans/agent.yaml +73 -0
new file mode 100644
@@ -0,0 +1,73 @@
1+# Plans — an agent that works through a list of tasks with its `plan` tool,
2+# and mm showing where it is after every plan action:
3+#
4+# 📋 plan: create
5+# 📋 [1/4] ⏳ Task 1: Create the Go module … (open)
6+# …
7+# 📋 [4/4] All tasks complete!
8+#
9+# The job: build a tiny Go program, step by step, in ./hello — module, code,
10+# test, run the tests, README. Needs Go on the PATH.
11+#
12+# llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
13+# llmman serve
14+#
15+# cd demos/plans
16+# ./reset.sh removes ./hello
17+# mm -tui agent.yaml or mm -p "…" agent.yaml
18+
19+provider: llmman
20+model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
21+baseUrl: http://127.0.0.1:17434/v1
22+fallback: http://host.docker.internal:17434/v1
23+contextWindow: 16384
24+
25+# The plan tool is on by default; written here because this demo is about it.
26+planTool: true
27+yolo: true
28+bashTool: true
29+bashTimeout: 2m
30+editTools: true
31+askUserTool: false
32+skillsDir: no-skills
33+
34+agentsMd:
35+ enabled: false
36+
37+sampling:
38+ temperature: 0.0
39+ parallel_tool_calls: false
40+ max_tokens: 2048
41+ chat_template_kwargs:
42+ enable_thinking: false
43+
44+maxTurns: 30
45+watchdogTimeout: 2m
46+firstTokenTimeout: 3m
47+displayCommands: true
48+
49+acp:
50+ name: planner
51+ title: 📋 Planner (plans demo)
52+
53+system: |
54+ Your name is Planner. You are a coding agent in a terminal, with a bash
55+ tool, file tools and a plan tool.
56+
57+ When the user gives you several tasks, work with a plan:
58+ 1. Call plan with action "create" and one task per item of the user's
59+ list, in order. Remember the plan name it returns.
60+ 2. For each task, in order: call plan "update" with status
61+ "in_progress", do the work with your other tools, then call plan
62+ "update" with status "done" — or "failed" with a short message if it
63+ could not be done, and go on with the next task.
64+ 3. When every task is over, answer in two or three sentences: what was
65+ done, and what failed if anything.
66+
67+ Never mark a task done before its work is done. Run commands to check
68+ what you did: do not guess their output.
69+
70+ Work only inside the project folder. If a task needs something that does
71+ not exist — a git remote, an account, a server, a file elsewhere — do not
72+ create a stand-in to make it pass: mark the task "failed" with a message
73+ saying what is missing, and go on.
new file mode 100644
@@ -0,0 +1,73 @@
1+# Plans — an agent that works through a list of tasks with its `plan` tool,
2+# and mm showing where it is after every plan action:
3+#
4+# 📋 plan: create
5+# 📋 [1/4] ⏳ Task 1: Create the Go module … (open)
6+# …
7+# 📋 [4/4] All tasks complete!
8+#
9+# The job: build a tiny Go program, step by step, in ./hello — module, code,
10+# test, run the tests, README. Needs Go on the PATH.
11+#
12+# llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
13+# llmman serve
14+#
15+# cd demos/plans
16+# ./reset.sh removes ./hello
17+# mm -tui agent.yaml or mm -p "…" agent.yaml
18+
19+provider: llmman
20+model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
21+baseUrl: http://127.0.0.1:17434/v1
22+fallback: http://host.docker.internal:17434/v1
23+contextWindow: 16384
24+
25+# The plan tool is on by default; written here because this demo is about it.
26+planTool: true
27+yolo: true
28+bashTool: true
29+bashTimeout: 2m
30+editTools: true
31+askUserTool: false
32+skillsDir: no-skills
33+
34+agentsMd:
35+ enabled: false
36+
37+sampling:
38+ temperature: 0.0
39+ parallel_tool_calls: false
40+ max_tokens: 2048
41+ chat_template_kwargs:
42+ enable_thinking: false
43+
44+maxTurns: 30
45+watchdogTimeout: 2m
46+firstTokenTimeout: 3m
47+displayCommands: true
48+
49+acp:
50+ name: planner
51+ title: 📋 Planner (plans demo)
52+
53+system: |
54+ Your name is Planner. You are a coding agent in a terminal, with a bash
55+ tool, file tools and a plan tool.
56+
57+ When the user gives you several tasks, work with a plan:
58+ 1. Call plan with action "create" and one task per item of the user's
59+ list, in order. Remember the plan name it returns.
60+ 2. For each task, in order: call plan "update" with status
61+ "in_progress", do the work with your other tools, then call plan
62+ "update" with status "done" — or "failed" with a short message if it
63+ could not be done, and go on with the next task.
64+ 3. When every task is over, answer in two or three sentences: what was
65+ done, and what failed if anything.
66+
67+ Never mark a task done before its work is done. Run commands to check
68+ what you did: do not guess their output.
69+
70+ Work only inside the project folder. If a task needs something that does
71+ not exist — a git remote, an account, a server, a file elsewhere — do not
72+ create a stand-in to make it pass: mark the task "failed" with a message
73+ saying what is missing, and go on.
added demos/plans/reset.sh +7 -0
new file mode 100755
@@ -0,0 +1,7 @@
1+#!/bin/sh
2+# reset.sh — puts the demo back as it ships: removes the ./hello project the
3+# agent builds, so the next run starts from nothing.
4+set -eu
5+cd "$(dirname "$0")"
6+rm -rf hello
7+echo "reset: ./hello removed"
new file mode 100755
@@ -0,0 +1,7 @@
1+#!/bin/sh
2+# reset.sh — puts the demo back as it ships: removes the ./hello project the
3+# agent builds, so the next run starts from nothing.
4+set -eu
5+cd "$(dirname "$0")"
6+rm -rf hello
7+echo "reset: ./hello removed"
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), the questions ↑ recalls, /memory and search_memory&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><mxCell id="e_tools_store" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="docsearch" value="&lt;b&gt;internal/docsearch&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;BM25 over the text files of folders: /docs, search_docs&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="1640" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="textfold" value="&lt;b&gt;internal/textfold&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;case and accents folded, rune for rune&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1640" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_docsearch_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_docsearch_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_textfold_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="textfold" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="docsearch"><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, /memory and search_memory&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><mxCell id="e_tools_store" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="docsearch" value="&lt;b&gt;internal/docsearch&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;BM25 over the text files of folders: /docs, search_docs&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="1640" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="textfold" value="&lt;b&gt;internal/textfold&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;case and accents folded, rune for rune&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1640" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_docsearch_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_docsearch_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_textfold_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="textfold" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_tools" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_tools" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="tools"><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), the questions ↑ recalls, /memory and search_memory&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><mxCell id="e_tools_store" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="docsearch" value="&lt;b&gt;internal/docsearch&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;BM25 over the text files of folders: /docs, search_docs&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="1640" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="textfold" value="&lt;b&gt;internal/textfold&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;case and accents folded, rune for rune&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1640" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_docsearch_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_docsearch_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_textfold_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="textfold" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="docsearch"><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, /memory and search_memory&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><mxCell id="e_tools_store" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="docsearch" value="&lt;b&gt;internal/docsearch&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;BM25 over the text files of folders: /docs, search_docs&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="1640" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="textfold" value="&lt;b&gt;internal/textfold&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;case and accents folded, rune for rune&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1640" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_docsearch_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_docsearch_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="docsearch" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_store_textfold" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="store" target="textfold"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_textfold_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="textfold" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_docsearch" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="docsearch"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_tools" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_tools" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file\ No newline at end of file
modified docs/en/README.md +2 -1
@@ -26,6 +26,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
2626 - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the `mini-me` kit from [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), the `go-toolchain` and `cli-tools` mixins under `sbx/`, window mode and editor
2727 - [Add a skill](how-to/add-a-skill.md)
2828 - [Declare a custom tool](how-to/declare-a-custom-tool.md)
29+- [Manage plans and tasks](how-to/manage-plans-and-tasks.md) — the `plan` tool: give a list, follow the `📋` progress line, plans per conversation
2930 - [Save an answer to a file](how-to/save-an-answer.md)
3031 - [Search a documentation folder](how-to/search-the-docs.md) — `/docs <words>`, the `search_docs` tool, `docSearch` (BM25, no model)
3132 - [Search earlier sessions](how-to/search-earlier-sessions.md) — `/memory <words>`, the `search_memory` tool, `sessions.searchMemory` / `sessions.autoSearchMemory`
@@ -54,7 +55,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
5455 - [Command line](reference/cli.md) — flags, environment variables, start-up banner
5556 - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys
5657 - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule
57-- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, declared tools
58+- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `plan`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, `search_docs`, `search_memory`, declared tools
5859 - [Skill file format](reference/skill-format.md)
5960 - [Hooks](reference/hooks.md) — the six events, the JSON a hook reads and writes, `matcher`, `timeout`, `onError`, validation errors
6061 - [Agent Client Protocol surface](reference/acp.md)
@@ -26,6 +26,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
26 - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the `mini-me` kit from [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), the `go-toolchain` and `cli-tools` mixins under `sbx/`, window mode and editor26 - [Run mini-me in a Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — the `mini-me` kit from [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), the `go-toolchain` and `cli-tools` mixins under `sbx/`, window mode and editor
27 - [Add a skill](how-to/add-a-skill.md)27 - [Add a skill](how-to/add-a-skill.md)
28 - [Declare a custom tool](how-to/declare-a-custom-tool.md)28 - [Declare a custom tool](how-to/declare-a-custom-tool.md)
29+- [Manage plans and tasks](how-to/manage-plans-and-tasks.md) — the `plan` tool: give a list, follow the `📋` progress line, plans per conversation
29 - [Save an answer to a file](how-to/save-an-answer.md)30 - [Save an answer to a file](how-to/save-an-answer.md)
30 - [Search a documentation folder](how-to/search-the-docs.md) — `/docs <words>`, the `search_docs` tool, `docSearch` (BM25, no model)31 - [Search a documentation folder](how-to/search-the-docs.md) — `/docs <words>`, the `search_docs` tool, `docSearch` (BM25, no model)
31 - [Search earlier sessions](how-to/search-earlier-sessions.md) — `/memory <words>`, the `search_memory` tool, `sessions.searchMemory` / `sessions.autoSearchMemory`32 - [Search earlier sessions](how-to/search-earlier-sessions.md) — `/memory <words>`, the `search_memory` tool, `sessions.searchMemory` / `sessions.autoSearchMemory`
@@ -54,7 +55,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
54 - [Command line](reference/cli.md) — flags, environment variables, start-up banner55 - [Command line](reference/cli.md) — flags, environment variables, start-up banner
55 - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys56 - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys
56 - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule57 - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule
57-- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, declared tools58+- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `plan`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, `search_docs`, `search_memory`, declared tools
58 - [Skill file format](reference/skill-format.md)59 - [Skill file format](reference/skill-format.md)
59 - [Hooks](reference/hooks.md) — the six events, the JSON a hook reads and writes, `matcher`, `timeout`, `onError`, validation errors60 - [Hooks](reference/hooks.md) — the six events, the JSON a hook reads and writes, `matcher`, `timeout`, `onError`, validation errors
60 - [Agent Client Protocol surface](reference/acp.md)61 - [Agent Client Protocol surface](reference/acp.md)
modified docs/en/how-to/manage-plans-and-tasks.md +42 -427
@@ -1,450 +1,65 @@
1----
2-title: Manage Plans and Tasks
3----
1+# How to manage plans and tasks
42
5-# Manage Plans and Tasks
3+This guide shows how to have the agent work through a list of tasks with its `plan` tool, and how to follow its progress. The tool is on by default (`planTool: true`).
64
7-Mini-me includes a built-in **plan** tool that lets the agent create, track, and manage multi-step todo lists. When you give the agent a list of tasks to do, it automatically creates a plan and tracks progress, showing you the current status after each response.
5+## Give the agent a list
86
9-## Quick Example
7+Ask for several things at once, as a list:
108
9+```text
10+Do these 2 things: 1. create a file a.txt containing A, 2. create a file b.txt containing B.
1111 ```
12-You: Do these 3 things: 1. Create a README file, 2. Add a license, 3. Commit the changes
1312
14-Agent: I'll handle this in 3 steps. Starting with: Create a README file
15-📋 plan: create user-request-1
13+The model creates a plan from the list, works through it, and updates each task's status as it goes. Small models do it more reliably when you say so: "Use the plan tool. Do these…".
1614
17-Agent: README file created.
18-📋 [2/3] 🔄 Task 2: Add a license (in_progress)
15+## Follow the progress
1916
20-Agent: License added.
21-📋 [3/3] 🔄 Task 3: Commit the changes (in_progress)
17+mm shows where the plan is after every plan action that changes or reads its state (`create`, `update`, `next`, `status`) — whether or not the model mentions it in its answer.
2218
23-Agent: All changes committed.
24-📋 [3/3] ✅ All tasks complete!
25-```
26-
27-The `📋` line shows you exactly where the agent is in your plan.
28-
29----
30-
31-## How It Works
32-
33-### The Plan Lifecycle
34-
35-1. **You state your request** as a list of tasks
36-2. **Agent detects the plan** and creates it using the `plan` tool
37-3. **Agent works on tasks** one by one, updating status
38-4. **Agent reports progress** after each action
39-5. **Plan completes** when all tasks are done
40-
41-### Status Icons
42-
43-| Icon | Meaning |
44-|------|---------|
45-| ⏳ | Task not started (open) |
46-| 🔄 | Task currently in progress |
47-| ✅ | Task completed successfully |
48-| ❌ | Task failed |
49-
50-### Status Line Format
51-
52-```
53-📋 [current/total] icon Task N: description (status)
54-```
55-
56-Examples:
57-- `📋 [1/5] ⏳ Task 1: Analyze the code (open)` - First task, not started yet
58-- `📋 [2/5] 🔄 Task 2: Fix the bugs (in_progress)` - Second task, currently working
59-- `📋 [5/5] ✅ All tasks complete!` - All done!
60-- `📋 [3/5] ❌ Task 3: Run tests (failed: connection timeout)` - Task failed with error
61-
62----
63-
64-## User Scenarios
65-
66-### Scenario 1: Numbered List
67-
68-**Your input:**
69-```
70-Do these 5 things:
71-1. Check the current version
72-2. Update dependencies
73-3. Run tests
74-4. Fix any failures
75-5. Deploy to staging
76-```
77-
78-**What happens:**
79-- Agent creates a plan with 5 tasks
80-- Works through each sequentially
81-- Shows progress: `📋 [1/5]`, `📋 [2/5]`, etc.
82-- Completes with: `📋 [5/5] ✅ All tasks complete!`
83-
84----
85-
86-### Scenario 2: Comma-Separated Tasks
87-
88-**Your input:**
89-```
90-Set up the project: create the directory, initialize git, and add a README
91-```
92-
93-**What happens:**
94-- Agent detects this as a multi-step request
95-- Creates a plan with 3 tasks
96-- Executes in order
97-
98----
99-
100-### Scenario 3: Multiple Plans
101-
102-You can have multiple active plans in a single session.
103-
104-**Your input:**
105-```
106-First, fix the auth module: 1. Find the bug, 2. Write the fix, 3. Test it
107-```
108-
109-Agent works on the auth plan, shows: `📋 [2/3] 🔄 Task 2: Write the fix (in_progress)`
110-
111-**Then you say:**
112-```
113-Also, update the documentation: 1. Update README, 2. Update CHANGELOG
114-```
115-
116-Agent creates a second plan and manages both independently.
117-
118-**To see all active plans:**
119-```
120-What plans do I have?
121-```
122-
123----
124-
125-## Interacting with Plans
126-
127-### Check Current Plan Status
128-
129-**Ask:** "What's the status of my plan?"
130-
131-**Agent responds:**
132-```
133-Current plan: [2/3] 🔄 Task 2: Write the fix (in_progress). Next: Test it
134-```
135-
136-### Skip a Task
137-
138-**Ask:** "Skip the test step, just deploy"
139-
140-**Agent:**
141-- Marks the current task as done
142-- Skips to the next task
143-- Confirms: "Skipping test step. Deploying to staging..."
144-
145-### Abandon a Plan
146-
147-**Ask:** "Never mind, I don't need this anymore"
148-
149-**Agent:**
150-- Asks for confirmation
151-- If you confirm, deletes the plan
152-- All progress is lost
153-
154----
155-
156-## Plan Management Commands
157-
158-While the plan tool is primarily model-driven (the agent uses it automatically), you can also interact with plans directly:
159-
160-### List All Plans
161-
162-**Command:** `/plans` (not yet implemented in v1 - coming in v2)
163-
164-**What it shows:**
165-```
166-Active plans:
167-1. setup-project (2/5 tasks complete)
168-2. fix-bugs (0/3 tasks complete)
169-```
170-
171-### View Plan Details
172-
173-**Ask the agent:** "Show me the details of plan X"
174-
175-**Agent responds:** Full task list with status for each task
176-
177-### Delete a Plan
178-
179-**Ask the agent:** "Delete plan X"
180-
181-**Agent:** Confirms and deletes the plan
182-
183----
184-
185-## Session Lifecycle
186-
187-### Plans and /new Command
188-
189-When you start a new session with `/new`:
190-- All active plans are **cleared**
191-- This is intentional: a new session means a fresh start
192-- If you want to continue an old plan, don't use `/new`
193-
194-### Plans and Agent Restart
195-
196-In v1 (current version):
197-- Plans are stored **in memory only**
198-- Plans are **lost** when the agent restarts
199-- This keeps v1 simple and fast
200-
201-In v2 (future):
202-- Plans will be persisted to session files
203-- Plans will survive agent restarts
204-
205----
206-
207-## The plan Tool (For Advanced Users)
208-
209-The `plan` tool is available for the model to call. It supports these actions:
210-
211-| Action | Purpose | Required Parameters |
212-|--------|---------|---------------------|
213-| `create` | Create a new plan | `tasks` (array of `{description}`) |
214-| `get` | Get plan details | `name` |
215-| `update` | Update task status | `name`, `taskId`, `status` |
216-| `next` | Move to next task | `name` |
217-| `list` | List all plans | none |
218-| `delete` | Delete a plan | `name` |
219-| `status` | Get status line | `name` |
19+- **Line mode** (`mm agent.yaml`, `mm -p`): one line before each call, one line after it. Measured on Qwen3.5 4B with the request above:
22020
221-### Status Values
21+ ```text
22+ 📋 plan: create
23+ 📋 [1/2] ⏳ Task 1: Create a file a.txt containing A (open)
24+ 💾 write_file: a.txt (1 bytes)
25+ 💾 write_file: b.txt (1 bytes)
26+ 📋 plan: update user-request-1
27+ 📋 [2/2] ⏳ Task 2: Create a file b.txt containing B (open)
28+ 📋 plan: update user-request-1
29+ 📋 [2/2] All tasks complete!
30+ ```
22231
223-- `open` - Task not started
224-- `in_progress` - Task currently being worked on
225-- `done` - Task completed successfully
226-- `failed` - Task failed
32+- **Window** (`mm -tui`): each call is a tool call in the conversation, and the `📋 [C/N] …` line follows it as a notice.
33+- **Editor** (`mm -acp`): each call is a tool call; the status line is inside its result.
22734
228----
35+The status line shows the task being worked on — the first one in progress, else the first one still open: `[C/N] <icon> Task C: <description> (<status>)`. Icons: ⏳ open, 🔄 in progress, ✅ done, ❌ failed. When every task is over it says `[N/N] All tasks complete!`, or `[N/N] Plan finished: D done, F failed` if some failed.
22936
230-## The plan-manager Skill
37+## Steer the plan
23138
232-The agent uses the `plan-manager` skill to know **when and how** to use the plan tool. This skill includes:
39+The model decides when to call the tool; you steer it in your own words:
23340
234-- **Detection patterns:** How to recognize when a user gives a multi-step request
235-- **Usage guidelines:** When to create plans, update tasks, move between tasks
236-- **Display format:** How to show plan status to the user
237-- **Error handling:** What to do when things go wrong
41+- **Where are we?** "What's the status of the plan?" — the model calls `status` and mm shows the line.
42+- **Skip a task.** "Skip the tests and go on." — the model can mark it and move on with `next`.
43+- **A task failed.** The model can mark it `failed`, with a note; the status line then moves to the next open task.
44+- **Drop the plan.** "Forget this plan." — the model can call `delete`.
23845
239-You don't need to manually load this skill - the agent does it automatically when appropriate.
46+There is no slash command for plans: they are the model's tool.
24047
241----
48+## Variants
24249
243-## Examples
244-
245-### Example 1: Bug Fixing Workflow
246-
247-```
248-You: Fix these issues in the auth module:
249- 1. Check for null pointer exceptions
250- 2. Add input validation
251- 3. Write unit tests
252- 4. Update the documentation
253-
254-Agent: I'll handle this in 4 steps. Starting with: Check for null pointer exceptions
255-📋 plan: create user-request-1
256-
257-... (agent checks code) ...
258-
259-Agent: Found 3 potential null pointer issues.
260-📋 [2/4] 🔄 Task 2: Add input validation (in_progress)
261-
262-... (agent adds validation) ...
263-
264-Agent: Input validation added to auth.go.
265-📋 [3/4] 🔄 Task 3: Write unit tests (in_progress)
266-
267-... (agent writes tests) ...
268-
269-Agent: Tests written and passing.
270-📋 [4/4] 🔄 Task 4: Update the documentation (in_progress)
271-
272-... (agent updates docs) ...
273-
274-Agent: Documentation updated with new validation requirements.
275-📋 [4/4] ✅ All tasks complete!
276-```
277-
278----
279-
280-### Example 2: Project Setup
281-
282-```
283-You: Set up a new Go project with:
284- 1. Module initialization
285- 2. Directory structure
286- 3. Main file
287- 4. Test file
288-
289-Agent: I'll set this up in 4 steps. Starting with: Module initialization
290-📋 plan: create user-request-1
291-
292-Agent: Module initialized with go mod
293-📋 [2/4] 🔄 Task 2: Directory structure (in_progress)
294-
295-Agent: Directories created (cmd, internal, pkg, tests)
296-📋 [3/4] 🔄 Task 3: Main file (in_progress)
297-
298-Agent: Main file created with basic structure
299-📋 [4/4] 🔄 Task 4: Test file (in_progress)
300-
301-Agent: Test file created with sample tests
302-📋 [4/4] ✅ All tasks complete!
303-```
304-
305----
306-
307-### Example 3: Code Review
308-
309-```
310-You: Review this PR and:
311- 1. Check for security issues
312- 2. Verify tests pass
313- 3. Review the documentation
314- 4. Leave comments
315-
316-Agent: I'll review this PR in 4 steps. Starting with: Check for security issues
317-📋 plan: create user-request-1
318-
319-Agent: No security issues found.
320-📋 [2/4] 🔄 Task 2: Verify tests pass (in_progress)
321-
322-Agent: All tests passing.
323-📋 [3/4] 🔄 Task 3: Review the documentation (in_progress)
324-
325-Agent: Documentation looks good, just needs a typo fix.
326-📋 [4/4] 🔄 Task 4: Leave comments (in_progress)
327-
328-Agent: Comments left on the PR.
329-📋 [4/4] ✅ All tasks complete!
330-```
331-
332----
333-
334-### Example 4: With a Task Failure
335-
336-```
337-You: Do these:
338- 1. Connect to the database
339- 2. Run the migration
340- 3. Import the data
341-
342-Agent: I'll handle this in 3 steps. Starting with: Connect to the database
343-📋 plan: create user-request-1
344-
345-Agent: Database connection established.
346-📋 [2/3] 🔄 Task 2: Run the migration (in_progress)
347-
348-Agent: Migration completed.
349-📋 [3/3] 🔄 Task 3: Import the data (in_progress)
350-
351-Agent: Error: connection timeout to data source
352-📋 plan: update user-request-1
353-
354-Agent: ⚠️ Task 3 failed: connection timeout to data source
355-📋 [3/3] ❌ Task 3: Import the data (failed: connection timeout)
356-
357-Agent: 2 of 3 tasks completed. The data import failed due to a connection timeout. Would you like me to retry, or skip this task?
358-```
359-
360----
361-
362-## Configuration
363-
364-The plan tool can be enabled or disabled via the YAML configuration file:
365-
366-```yaml
367-# In your agent.yaml file
368-planTool: true # Enabled by default
369-```
370-
371-To disable:
372-```yaml
373-planTool: false
374-```
375-
376-When disabled, the agent will not have access to the `plan` tool and will not automatically create or track plans.
377-
378----
50+- **Several conversations in an editor.** Each thread has its own plans: a plan made in one Zed thread is not visible in another.
51+- **Starting over.** `/new` and `/resume` forget the plans in every front end; an editor's reload (`session/load`, `session/resume`) starts the thread with no plan. Plans are kept in memory only, never in the session file, and are lost when mm stops.
52+- **The `plan-manager` skill.** `skills/plan-manager` tells the model when to make a plan and how to move through it. Put it in the agent's `skillsDir` for a model that does not use the tool by itself. It also asks the model to repeat the status line in its answer; mm shows it anyway.
53+- **An agent that does not need plans.** Set `planTool: false`: the tool is not declared. Do it for an agent whose job is a single answer (writing a document, answering questions), where a small model may otherwise loop on plan calls.
37954
38055 ## Troubleshooting
38156
382-### Agent Not Creating Plans
383-
384-If the agent isn't creating plans for your multi-step requests:
385-
386-1. **Make your request clearer** - Use numbered lists
387- - ❌ "Fix some things"
388- - ✅ "Do these: 1. Fix A, 2. Fix B, 3. Fix C"
389-
390-2. **Be more explicit** - Use words like "plan", "steps", "then"
391- - ✅ "My plan is to first do X, then do Y"
392-
393-3. **Check the skill is loaded** - The agent should automatically load the `plan-manager` skill when it detects a plan-like request
394-
395-### The Agent Keeps Calling `plan update`
396-
397-`update` changes the status of one task; it cannot change a plan's tasks. A model that sends an `update` without a valid `status` — often with a `tasks` list, trying to edit the plan — gets an answer that quotes its call back, names the missing field, says that `tasks` is only read by `create`, and gives a valid call:
398-
399-```
400-[error: plan update changes the status of ONE task, and "status" is missing. You sent: {…}. "status" must be one of: open, in_progress, done, failed. "tasks" is ignored by update (only create reads it): to change a plan's tasks, delete the plan and create it again. A valid call: {"action":"update","name":"user-request-1","taskId":1,"status":"done"}]
401-```
402-
403-If a small model still repeats the call, set `planTool: false` for an agent whose job does not need plans (writing a document, answering questions): the model then works without them.
404-
405-### Plans Disappearing
406-
407-In v1, plans are stored in memory and will be lost when:
408-- You start a new session with `/new`
409-- The agent restarts
410-- You close and reopen the terminal
411-
412-This is expected behavior in v1. Session persistence is coming in v2.
413-
414----
415-
416-## Best Practices
417-
418-1. **Be specific** - The more detail in your task descriptions, the better the agent can execute them
419-
420-2. **Keep plans focused** - One plan per logical request. Don't mix unrelated tasks
421-
422-3. **Check progress** - The status line shows you exactly where the agent is
423-
424-4. **Ask for clarification** - If the agent seems stuck or confused, ask "What's the status?"
425-
426-5. **Start small** - For complex multi-step work, start with a few tasks and add more as needed
427-
428----
429-
430-## Comparison with Other Tools
431-
432-| Feature | plan tool | skills | slash commands |
433-|---------|-----------|--------|----------------|
434-| Purpose | Track multi-step work | Load procedures | User commands |
435-| Trigger | Automatic (model detects) | Explicit (`/skill`) | Explicit (`/command`) |
436-| Persistence (v1) | In-memory, session only | File-based | N/A |
437-| User interaction | Indirect (via model) | Indirect (model loads) | Direct |
438-
439-The `plan` tool complements other mini-me features:
440-- Use **skills** for complex, reusable procedures
441-- Use **plan tool** for ad-hoc multi-step requests
442-- Use **slash commands** for direct user actions
443-
444----
57+- **The agent does not create a plan.** Give a numbered list, and say "use the plan tool". With a small model, add the `plan-manager` skill.
58+- **The agent keeps calling `plan update`.** `update` changes the status of ONE task; it cannot change a plan's tasks. A call without a valid `status` gets an answer that quotes the call back, names the missing field and gives a valid call. If a small model still repeats it, set `planTool: false` for that agent.
44559
446-## See Also
60+## See also
44761
448-- [Slash Commands Reference](../reference/slash-commands.md) - For user commands
449-- [Tools Reference](../reference/tools.md) - For all built-in tools
450-- [Skills Overview](../explanation/skills.md) - For the skill system
62+- [Tools reference — `plan`](../reference/tools.md#plan): every action, field, result and error
63+- [Configuration reference](../reference/configuration.md): `planTool`
64+- [Slash commands reference](../reference/slash-commands.md): `/new`, `/resume`
65+- `demos/plans/` — a measured demo: a Go program built in four planned steps, and a plan that ends with a failed task
@@ -1,450 +1,65 @@
1----1+# How to manage plans and tasks
2-title: Manage Plans and Tasks
3----
4 2
5-# Manage Plans and Tasks3+This guide shows how to have the agent work through a list of tasks with its `plan` tool, and how to follow its progress. The tool is on by default (`planTool: true`).
6 4
7-Mini-me includes a built-in **plan** tool that lets the agent create, track, and manage multi-step todo lists. When you give the agent a list of tasks to do, it automatically creates a plan and tracks progress, showing you the current status after each response.5+## Give the agent a list
8 6
9-## Quick Example7+Ask for several things at once, as a list:
10 8
9+```text
10+Do these 2 things: 1. create a file a.txt containing A, 2. create a file b.txt containing B.
11 ```11 ```
12-You: Do these 3 things: 1. Create a README file, 2. Add a license, 3. Commit the changes
13 12
14-Agent: I'll handle this in 3 steps. Starting with: Create a README file13+The model creates a plan from the list, works through it, and updates each task's status as it goes. Small models do it more reliably when you say so: "Use the plan tool. Do these…".
15-📋 plan: create user-request-1
16 14
17-Agent: README file created.15+## Follow the progress
18-📋 [2/3] 🔄 Task 2: Add a license (in_progress)
19 16
20-Agent: License added.17+mm shows where the plan is after every plan action that changes or reads its state (`create`, `update`, `next`, `status`) — whether or not the model mentions it in its answer.
21-📋 [3/3] 🔄 Task 3: Commit the changes (in_progress)
22 18
23-Agent: All changes committed.19+- **Line mode** (`mm agent.yaml`, `mm -p`): one line before each call, one line after it. Measured on Qwen3.5 4B with the request above:
24-📋 [3/3] ✅ All tasks complete!
25-```
26-
27-The `📋` line shows you exactly where the agent is in your plan.
28-
29----
30-
31-## How It Works
32-
33-### The Plan Lifecycle
34-
35-1. **You state your request** as a list of tasks
36-2. **Agent detects the plan** and creates it using the `plan` tool
37-3. **Agent works on tasks** one by one, updating status
38-4. **Agent reports progress** after each action
39-5. **Plan completes** when all tasks are done
40-
41-### Status Icons
42-
43-| Icon | Meaning |
44-|------|---------|
45-| ⏳ | Task not started (open) |
46-| 🔄 | Task currently in progress |
47-| ✅ | Task completed successfully |
48-| ❌ | Task failed |
49-
50-### Status Line Format
51-
52-```
53-📋 [current/total] icon Task N: description (status)
54-```
55-
56-Examples:
57-- `📋 [1/5] ⏳ Task 1: Analyze the code (open)` - First task, not started yet
58-- `📋 [2/5] 🔄 Task 2: Fix the bugs (in_progress)` - Second task, currently working
59-- `📋 [5/5] ✅ All tasks complete!` - All done!
60-- `📋 [3/5] ❌ Task 3: Run tests (failed: connection timeout)` - Task failed with error
61-
62----
63-
64-## User Scenarios
65-
66-### Scenario 1: Numbered List
67-
68-**Your input:**
69-```
70-Do these 5 things:
71-1. Check the current version
72-2. Update dependencies
73-3. Run tests
74-4. Fix any failures
75-5. Deploy to staging
76-```
77-
78-**What happens:**
79-- Agent creates a plan with 5 tasks
80-- Works through each sequentially
81-- Shows progress: `📋 [1/5]`, `📋 [2/5]`, etc.
82-- Completes with: `📋 [5/5] ✅ All tasks complete!`
83-
84----
85-
86-### Scenario 2: Comma-Separated Tasks
87-
88-**Your input:**
89-```
90-Set up the project: create the directory, initialize git, and add a README
91-```
92-
93-**What happens:**
94-- Agent detects this as a multi-step request
95-- Creates a plan with 3 tasks
96-- Executes in order
97-
98----
99-
100-### Scenario 3: Multiple Plans
101-
102-You can have multiple active plans in a single session.
103-
104-**Your input:**
105-```
106-First, fix the auth module: 1. Find the bug, 2. Write the fix, 3. Test it
107-```
108-
109-Agent works on the auth plan, shows: `📋 [2/3] 🔄 Task 2: Write the fix (in_progress)`
110-
111-**Then you say:**
112-```
113-Also, update the documentation: 1. Update README, 2. Update CHANGELOG
114-```
115-
116-Agent creates a second plan and manages both independently.
117-
118-**To see all active plans:**
119-```
120-What plans do I have?
121-```
122-
123----
124-
125-## Interacting with Plans
126-
127-### Check Current Plan Status
128-
129-**Ask:** "What's the status of my plan?"
130-
131-**Agent responds:**
132-```
133-Current plan: [2/3] 🔄 Task 2: Write the fix (in_progress). Next: Test it
134-```
135-
136-### Skip a Task
137-
138-**Ask:** "Skip the test step, just deploy"
139-
140-**Agent:**
141-- Marks the current task as done
142-- Skips to the next task
143-- Confirms: "Skipping test step. Deploying to staging..."
144-
145-### Abandon a Plan
146-
147-**Ask:** "Never mind, I don't need this anymore"
148-
149-**Agent:**
150-- Asks for confirmation
151-- If you confirm, deletes the plan
152-- All progress is lost
153-
154----
155-
156-## Plan Management Commands
157-
158-While the plan tool is primarily model-driven (the agent uses it automatically), you can also interact with plans directly:
159-
160-### List All Plans
161-
162-**Command:** `/plans` (not yet implemented in v1 - coming in v2)
163-
164-**What it shows:**
165-```
166-Active plans:
167-1. setup-project (2/5 tasks complete)
168-2. fix-bugs (0/3 tasks complete)
169-```
170-
171-### View Plan Details
172-
173-**Ask the agent:** "Show me the details of plan X"
174-
175-**Agent responds:** Full task list with status for each task
176-
177-### Delete a Plan
178-
179-**Ask the agent:** "Delete plan X"
180-
181-**Agent:** Confirms and deletes the plan
182-
183----
184-
185-## Session Lifecycle
186-
187-### Plans and /new Command
188-
189-When you start a new session with `/new`:
190-- All active plans are **cleared**
191-- This is intentional: a new session means a fresh start
192-- If you want to continue an old plan, don't use `/new`
193-
194-### Plans and Agent Restart
195-
196-In v1 (current version):
197-- Plans are stored **in memory only**
198-- Plans are **lost** when the agent restarts
199-- This keeps v1 simple and fast
200-
201-In v2 (future):
202-- Plans will be persisted to session files
203-- Plans will survive agent restarts
204-
205----
206-
207-## The plan Tool (For Advanced Users)
208-
209-The `plan` tool is available for the model to call. It supports these actions:
210-
211-| Action | Purpose | Required Parameters |
212-|--------|---------|---------------------|
213-| `create` | Create a new plan | `tasks` (array of `{description}`) |
214-| `get` | Get plan details | `name` |
215-| `update` | Update task status | `name`, `taskId`, `status` |
216-| `next` | Move to next task | `name` |
217-| `list` | List all plans | none |
218-| `delete` | Delete a plan | `name` |
219-| `status` | Get status line | `name` |
220 20
221-### Status Values21+ ```text
22+ 📋 plan: create
23+ 📋 [1/2] ⏳ Task 1: Create a file a.txt containing A (open)
24+ 💾 write_file: a.txt (1 bytes)
25+ 💾 write_file: b.txt (1 bytes)
26+ 📋 plan: update user-request-1
27+ 📋 [2/2] ⏳ Task 2: Create a file b.txt containing B (open)
28+ 📋 plan: update user-request-1
29+ 📋 [2/2] All tasks complete!
30+ ```
222 31
223-- `open` - Task not started32+- **Window** (`mm -tui`): each call is a tool call in the conversation, and the `📋 [C/N] …` line follows it as a notice.
224-- `in_progress` - Task currently being worked on33+- **Editor** (`mm -acp`): each call is a tool call; the status line is inside its result.
225-- `done` - Task completed successfully
226-- `failed` - Task failed
227 34
228----35+The status line shows the task being worked on — the first one in progress, else the first one still open: `[C/N] <icon> Task C: <description> (<status>)`. Icons: ⏳ open, 🔄 in progress, ✅ done, ❌ failed. When every task is over it says `[N/N] All tasks complete!`, or `[N/N] Plan finished: D done, F failed` if some failed.
229 36
230-## The plan-manager Skill37+## Steer the plan
231 38
232-The agent uses the `plan-manager` skill to know **when and how** to use the plan tool. This skill includes:39+The model decides when to call the tool; you steer it in your own words:
233 40
234-- **Detection patterns:** How to recognize when a user gives a multi-step request41+- **Where are we?** "What's the status of the plan?" — the model calls `status` and mm shows the line.
235-- **Usage guidelines:** When to create plans, update tasks, move between tasks42+- **Skip a task.** "Skip the tests and go on." — the model can mark it and move on with `next`.
236-- **Display format:** How to show plan status to the user43+- **A task failed.** The model can mark it `failed`, with a note; the status line then moves to the next open task.
237-- **Error handling:** What to do when things go wrong44+- **Drop the plan.** "Forget this plan." — the model can call `delete`.
238 45
239-You don't need to manually load this skill - the agent does it automatically when appropriate.46+There is no slash command for plans: they are the model's tool.
240 47
241----48+## Variants
242 49
243-## Examples50+- **Several conversations in an editor.** Each thread has its own plans: a plan made in one Zed thread is not visible in another.
244-51+- **Starting over.** `/new` and `/resume` forget the plans in every front end; an editor's reload (`session/load`, `session/resume`) starts the thread with no plan. Plans are kept in memory only, never in the session file, and are lost when mm stops.
245-### Example 1: Bug Fixing Workflow52+- **The `plan-manager` skill.** `skills/plan-manager` tells the model when to make a plan and how to move through it. Put it in the agent's `skillsDir` for a model that does not use the tool by itself. It also asks the model to repeat the status line in its answer; mm shows it anyway.
246-53+- **An agent that does not need plans.** Set `planTool: false`: the tool is not declared. Do it for an agent whose job is a single answer (writing a document, answering questions), where a small model may otherwise loop on plan calls.
247-```
248-You: Fix these issues in the auth module:
249- 1. Check for null pointer exceptions
250- 2. Add input validation
251- 3. Write unit tests
252- 4. Update the documentation
253-
254-Agent: I'll handle this in 4 steps. Starting with: Check for null pointer exceptions
255-📋 plan: create user-request-1
256-
257-... (agent checks code) ...
258-
259-Agent: Found 3 potential null pointer issues.
260-📋 [2/4] 🔄 Task 2: Add input validation (in_progress)
261-
262-... (agent adds validation) ...
263-
264-Agent: Input validation added to auth.go.
265-📋 [3/4] 🔄 Task 3: Write unit tests (in_progress)
266-
267-... (agent writes tests) ...
268-
269-Agent: Tests written and passing.
270-📋 [4/4] 🔄 Task 4: Update the documentation (in_progress)
271-
272-... (agent updates docs) ...
273-
274-Agent: Documentation updated with new validation requirements.
275-📋 [4/4] ✅ All tasks complete!
276-```
277-
278----
279-
280-### Example 2: Project Setup
281-
282-```
283-You: Set up a new Go project with:
284- 1. Module initialization
285- 2. Directory structure
286- 3. Main file
287- 4. Test file
288-
289-Agent: I'll set this up in 4 steps. Starting with: Module initialization
290-📋 plan: create user-request-1
291-
292-Agent: Module initialized with go mod
293-📋 [2/4] 🔄 Task 2: Directory structure (in_progress)
294-
295-Agent: Directories created (cmd, internal, pkg, tests)
296-📋 [3/4] 🔄 Task 3: Main file (in_progress)
297-
298-Agent: Main file created with basic structure
299-📋 [4/4] 🔄 Task 4: Test file (in_progress)
300-
301-Agent: Test file created with sample tests
302-📋 [4/4] ✅ All tasks complete!
303-```
304-
305----
306-
307-### Example 3: Code Review
308-
309-```
310-You: Review this PR and:
311- 1. Check for security issues
312- 2. Verify tests pass
313- 3. Review the documentation
314- 4. Leave comments
315-
316-Agent: I'll review this PR in 4 steps. Starting with: Check for security issues
317-📋 plan: create user-request-1
318-
319-Agent: No security issues found.
320-📋 [2/4] 🔄 Task 2: Verify tests pass (in_progress)
321-
322-Agent: All tests passing.
323-📋 [3/4] 🔄 Task 3: Review the documentation (in_progress)
324-
325-Agent: Documentation looks good, just needs a typo fix.
326-📋 [4/4] 🔄 Task 4: Leave comments (in_progress)
327-
328-Agent: Comments left on the PR.
329-📋 [4/4] ✅ All tasks complete!
330-```
331-
332----
333-
334-### Example 4: With a Task Failure
335-
336-```
337-You: Do these:
338- 1. Connect to the database
339- 2. Run the migration
340- 3. Import the data
341-
342-Agent: I'll handle this in 3 steps. Starting with: Connect to the database
343-📋 plan: create user-request-1
344-
345-Agent: Database connection established.
346-📋 [2/3] 🔄 Task 2: Run the migration (in_progress)
347-
348-Agent: Migration completed.
349-📋 [3/3] 🔄 Task 3: Import the data (in_progress)
350-
351-Agent: Error: connection timeout to data source
352-📋 plan: update user-request-1
353-
354-Agent: ⚠️ Task 3 failed: connection timeout to data source
355-📋 [3/3] ❌ Task 3: Import the data (failed: connection timeout)
356-
357-Agent: 2 of 3 tasks completed. The data import failed due to a connection timeout. Would you like me to retry, or skip this task?
358-```
359-
360----
361-
362-## Configuration
363-
364-The plan tool can be enabled or disabled via the YAML configuration file:
365-
366-```yaml
367-# In your agent.yaml file
368-planTool: true # Enabled by default
369-```
370-
371-To disable:
372-```yaml
373-planTool: false
374-```
375-
376-When disabled, the agent will not have access to the `plan` tool and will not automatically create or track plans.
377-
378----
379 54
380 ## Troubleshooting55 ## Troubleshooting
381 56
382-### Agent Not Creating Plans57+- **The agent does not create a plan.** Give a numbered list, and say "use the plan tool". With a small model, add the `plan-manager` skill.
383-58+- **The agent keeps calling `plan update`.** `update` changes the status of ONE task; it cannot change a plan's tasks. A call without a valid `status` gets an answer that quotes the call back, names the missing field and gives a valid call. If a small model still repeats it, set `planTool: false` for that agent.
384-If the agent isn't creating plans for your multi-step requests:
385-
386-1. **Make your request clearer** - Use numbered lists
387- - ❌ "Fix some things"
388- - ✅ "Do these: 1. Fix A, 2. Fix B, 3. Fix C"
389-
390-2. **Be more explicit** - Use words like "plan", "steps", "then"
391- - ✅ "My plan is to first do X, then do Y"
392-
393-3. **Check the skill is loaded** - The agent should automatically load the `plan-manager` skill when it detects a plan-like request
394-
395-### The Agent Keeps Calling `plan update`
396-
397-`update` changes the status of one task; it cannot change a plan's tasks. A model that sends an `update` without a valid `status` — often with a `tasks` list, trying to edit the plan — gets an answer that quotes its call back, names the missing field, says that `tasks` is only read by `create`, and gives a valid call:
398-
399-```
400-[error: plan update changes the status of ONE task, and "status" is missing. You sent: {…}. "status" must be one of: open, in_progress, done, failed. "tasks" is ignored by update (only create reads it): to change a plan's tasks, delete the plan and create it again. A valid call: {"action":"update","name":"user-request-1","taskId":1,"status":"done"}]
401-```
402-
403-If a small model still repeats the call, set `planTool: false` for an agent whose job does not need plans (writing a document, answering questions): the model then works without them.
404-
405-### Plans Disappearing
406-
407-In v1, plans are stored in memory and will be lost when:
408-- You start a new session with `/new`
409-- The agent restarts
410-- You close and reopen the terminal
411-
412-This is expected behavior in v1. Session persistence is coming in v2.
413-
414----
415-
416-## Best Practices
417-
418-1. **Be specific** - The more detail in your task descriptions, the better the agent can execute them
419-
420-2. **Keep plans focused** - One plan per logical request. Don't mix unrelated tasks
421-
422-3. **Check progress** - The status line shows you exactly where the agent is
423-
424-4. **Ask for clarification** - If the agent seems stuck or confused, ask "What's the status?"
425-
426-5. **Start small** - For complex multi-step work, start with a few tasks and add more as needed
427-
428----
429-
430-## Comparison with Other Tools
431-
432-| Feature | plan tool | skills | slash commands |
433-|---------|-----------|--------|----------------|
434-| Purpose | Track multi-step work | Load procedures | User commands |
435-| Trigger | Automatic (model detects) | Explicit (`/skill`) | Explicit (`/command`) |
436-| Persistence (v1) | In-memory, session only | File-based | N/A |
437-| User interaction | Indirect (via model) | Indirect (model loads) | Direct |
438-
439-The `plan` tool complements other mini-me features:
440-- Use **skills** for complex, reusable procedures
441-- Use **plan tool** for ad-hoc multi-step requests
442-- Use **slash commands** for direct user actions
443-
444----
445 59
446-## See Also60+## See also
447 61
448-- [Slash Commands Reference](../reference/slash-commands.md) - For user commands62+- [Tools reference — `plan`](../reference/tools.md#plan): every action, field, result and error
449-- [Tools Reference](../reference/tools.md) - For all built-in tools63+- [Configuration reference](../reference/configuration.md): `planTool`
450-- [Skills Overview](../explanation/skills.md) - For the skill system64+- [Slash commands reference](../reference/slash-commands.md): `/new`, `/resume`
65+- `demos/plans/` — a measured demo: a Go program built in four planned steps, and a plan that ends with a failed task
modified docs/en/reference/slash-commands.md +3 -3
@@ -9,9 +9,9 @@
99 | `/quit` | Exits the program. | Cancels the generation, prints `Goodbye.`, exits. |
1010 | `/abort` | Prints `⚠️ No generation in progress.` | Cancels the generation, prints `🛑 Aborting...`; the commands already run stay in the history. |
1111 | `/compact` | Compresses the history now, regardless of the threshold; prints the `🗜️` report, or `🗜️ nothing to compact` when no turn is older than `context.keepLastTurns`. | Sent to the model as a new message (see below). |
12-| `/new` | Clears the history back to the system prompt, resets the loop detector and forgets the server's last token count; prints `🆕 new session: N message(s) forgotten` (N counts every message except the system prompt). | Treated like any other text: cancels the generation, keeps its partial history, then runs as `/new` on that history. |
12+| `/new` | Clears the history back to the system prompt, resets the loop detector, forgets the server's last token count and the conversation's plans; prints `🆕 new session: N message(s) forgotten` (N counts every message except the system prompt). | Treated like any other text: cancels the generation, keeps its partial history, then runs as `/new` on that history. |
1313 | `/sessions` | Lists the project's stored sessions, newest first: id, last update, question count, first question, then ` · ⚠️ N error(s)` when the session recorded errors; `*` marks the current one. With `sessions.enabled: false`: `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Sent to the model as a new message. |
14-| `/resume [id]` | Continues a stored session: the most recently updated one other than the current, skipping empty ones, or the named id. Prints `↩️ resumed <id> — N message(s), started with "<first question>"` (plus ` · ⚠️ N error(s)` when the session recorded errors); resets the loop detector and the server's token count; later turns are saved to that session. Refused with a `⚠️` reason when sessions are off, when there is no earlier session, when the id is unknown or is the current session. | Sent to the model as a new message. |
14+| `/resume [id]` | Continues a stored session: the most recently updated one other than the current, skipping empty ones, or the named id. Prints `↩️ resumed <id> — N message(s), started with "<first question>"` (plus ` · ⚠️ N error(s)` when the session recorded errors); resets the loop detector, the server's token count and the plans; later turns are saved to that session. Refused with a `⚠️` reason when sessions are off, when there is no earlier session, when the id is unknown or is the current session. | Sent to the model as a new message. |
1515 | `/help` | Prints the commands of this front end, one per line with their description, aligned. | Sent to the model as a new message. |
1616 | `/copy [n\|all]` | Copies the last answer (`n`: the n-th from the end) to the clipboard through `pbcopy`/`wl-copy`/`xclip`/`xsel` or, failing that, OSC 52 when stdout is a terminal, and prints `📋 copied N chars (<road>)` or `⚠️ …`. `/copy all` is window-mode only: here it says so. | Sent to the model as a new message. |
1717 | `/save <file> [n]` | Writes the last answer (`n`: the n-th from the end) as it is to a new file, creating the missing directories, and prints `💾 saved the last answer to <file> (N bytes)`. Never overwrites: an existing file gives `⚠️ <file> already exists — nothing was written; choose another name`. A file name with spaces is not supported. The model is not called. | Sent to the model as a new message. |
@@ -176,7 +176,7 @@ Two commands exist in ACP mode, three with `unloadModels` (the default): `/new`,
176176
177177 | Command | Over ACP |
178178 |---------|----------|
179-| `/new` | Same as in the terminal: history back to the system prompt, token count forgotten; the session id, `cwd` and allow-always grants are kept. |
179+| `/new` | Same as in the terminal: history back to the system prompt, token count and this session's plans forgotten (other sessions keep theirs); the session id, `cwd` and allow-always grants are kept. |
180180 | `/compact` | Same as in the terminal, always forced: `🗜️ compressed …`, `🗜️ nothing to compact: …` or `[compact: failed, history kept: …]`. Waits for a running turn to finish first. |
181181 | `/memory <words>` | With `sessions.enabled` and `sessions.searchMemory`, advertised with the input hint `<words>`: same as in the terminal, in the session's project (`sessions.dir` under its `cwd`), as the agent's text; the history is not touched. |
182182 | `/docs <words>` | With `docSearch.searchDocs`, advertised with the input hint `<words>`: same as in the terminal, from the session's `cwd`, as the agent's text; the history is not touched. |
@@ -9,9 +9,9 @@
9 | `/quit` | Exits the program. | Cancels the generation, prints `Goodbye.`, exits. |9 | `/quit` | Exits the program. | Cancels the generation, prints `Goodbye.`, exits. |
10 | `/abort` | Prints `⚠️ No generation in progress.` | Cancels the generation, prints `🛑 Aborting...`; the commands already run stay in the history. |10 | `/abort` | Prints `⚠️ No generation in progress.` | Cancels the generation, prints `🛑 Aborting...`; the commands already run stay in the history. |
11 | `/compact` | Compresses the history now, regardless of the threshold; prints the `🗜️` report, or `🗜️ nothing to compact` when no turn is older than `context.keepLastTurns`. | Sent to the model as a new message (see below). |11 | `/compact` | Compresses the history now, regardless of the threshold; prints the `🗜️` report, or `🗜️ nothing to compact` when no turn is older than `context.keepLastTurns`. | Sent to the model as a new message (see below). |
12-| `/new` | Clears the history back to the system prompt, resets the loop detector and forgets the server's last token count; prints `🆕 new session: N message(s) forgotten` (N counts every message except the system prompt). | Treated like any other text: cancels the generation, keeps its partial history, then runs as `/new` on that history. |12+| `/new` | Clears the history back to the system prompt, resets the loop detector, forgets the server's last token count and the conversation's plans; prints `🆕 new session: N message(s) forgotten` (N counts every message except the system prompt). | Treated like any other text: cancels the generation, keeps its partial history, then runs as `/new` on that history. |
13 | `/sessions` | Lists the project's stored sessions, newest first: id, last update, question count, first question, then ` · ⚠️ N error(s)` when the session recorded errors; `*` marks the current one. With `sessions.enabled: false`: `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Sent to the model as a new message. |13 | `/sessions` | Lists the project's stored sessions, newest first: id, last update, question count, first question, then ` · ⚠️ N error(s)` when the session recorded errors; `*` marks the current one. With `sessions.enabled: false`: `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Sent to the model as a new message. |
14-| `/resume [id]` | Continues a stored session: the most recently updated one other than the current, skipping empty ones, or the named id. Prints `↩️ resumed <id> — N message(s), started with "<first question>"` (plus ` · ⚠️ N error(s)` when the session recorded errors); resets the loop detector and the server's token count; later turns are saved to that session. Refused with a `⚠️` reason when sessions are off, when there is no earlier session, when the id is unknown or is the current session. | Sent to the model as a new message. |14+| `/resume [id]` | Continues a stored session: the most recently updated one other than the current, skipping empty ones, or the named id. Prints `↩️ resumed <id> — N message(s), started with "<first question>"` (plus ` · ⚠️ N error(s)` when the session recorded errors); resets the loop detector, the server's token count and the plans; later turns are saved to that session. Refused with a `⚠️` reason when sessions are off, when there is no earlier session, when the id is unknown or is the current session. | Sent to the model as a new message. |
15 | `/help` | Prints the commands of this front end, one per line with their description, aligned. | Sent to the model as a new message. |15 | `/help` | Prints the commands of this front end, one per line with their description, aligned. | Sent to the model as a new message. |
16 | `/copy [n\|all]` | Copies the last answer (`n`: the n-th from the end) to the clipboard through `pbcopy`/`wl-copy`/`xclip`/`xsel` or, failing that, OSC 52 when stdout is a terminal, and prints `📋 copied N chars (<road>)` or `⚠️ …`. `/copy all` is window-mode only: here it says so. | Sent to the model as a new message. |16 | `/copy [n\|all]` | Copies the last answer (`n`: the n-th from the end) to the clipboard through `pbcopy`/`wl-copy`/`xclip`/`xsel` or, failing that, OSC 52 when stdout is a terminal, and prints `📋 copied N chars (<road>)` or `⚠️ …`. `/copy all` is window-mode only: here it says so. | Sent to the model as a new message. |
17 | `/save <file> [n]` | Writes the last answer (`n`: the n-th from the end) as it is to a new file, creating the missing directories, and prints `💾 saved the last answer to <file> (N bytes)`. Never overwrites: an existing file gives `⚠️ <file> already exists — nothing was written; choose another name`. A file name with spaces is not supported. The model is not called. | Sent to the model as a new message. |17 | `/save <file> [n]` | Writes the last answer (`n`: the n-th from the end) as it is to a new file, creating the missing directories, and prints `💾 saved the last answer to <file> (N bytes)`. Never overwrites: an existing file gives `⚠️ <file> already exists — nothing was written; choose another name`. A file name with spaces is not supported. The model is not called. | Sent to the model as a new message. |
@@ -176,7 +176,7 @@ Two commands exist in ACP mode, three with `unloadModels` (the default): `/new`,
176 176
177 | Command | Over ACP |177 | Command | Over ACP |
178 |---------|----------|178 |---------|----------|
179-| `/new` | Same as in the terminal: history back to the system prompt, token count forgotten; the session id, `cwd` and allow-always grants are kept. |179+| `/new` | Same as in the terminal: history back to the system prompt, token count and this session's plans forgotten (other sessions keep theirs); the session id, `cwd` and allow-always grants are kept. |
180 | `/compact` | Same as in the terminal, always forced: `🗜️ compressed …`, `🗜️ nothing to compact: …` or `[compact: failed, history kept: …]`. Waits for a running turn to finish first. |180 | `/compact` | Same as in the terminal, always forced: `🗜️ compressed …`, `🗜️ nothing to compact: …` or `[compact: failed, history kept: …]`. Waits for a running turn to finish first. |
181 | `/memory <words>` | With `sessions.enabled` and `sessions.searchMemory`, advertised with the input hint `<words>`: same as in the terminal, in the session's project (`sessions.dir` under its `cwd`), as the agent's text; the history is not touched. |181 | `/memory <words>` | With `sessions.enabled` and `sessions.searchMemory`, advertised with the input hint `<words>`: same as in the terminal, in the session's project (`sessions.dir` under its `cwd`), as the agent's text; the history is not touched. |
182 | `/docs <words>` | With `docSearch.searchDocs`, advertised with the input hint `<words>`: same as in the terminal, from the session's `cwd`, as the agent's text; the history is not touched. |182 | `/docs <words>` | With `docSearch.searchDocs`, advertised with the input hint `<words>`: same as in the terminal, from the session's `cwd`, as the agent's text; the history is not touched. |
modified docs/en/reference/tools.md +32 -0
@@ -132,6 +132,38 @@ the closest the file comes to it — copy these lines:
132132 │ 6 │ }
133133 ```
134134
135+## `plan`
136+
137+Declared when `planTool: true` (the default). Turns a list of tasks into a plan the model updates as it works. See [Manage plans and tasks](../how-to/manage-plans-and-tasks.md).
138+
139+| Field | Type | Description |
140+|-------|------|-------------|
141+| `action` | string | Required. `create`, `get`, `update`, `next`, `list`, `delete` or `status`. |
142+| `name` | string | The plan. For `create`, optional: a plan without a name is called `user-request-N` (N counts the conversation's plans). For the other actions, optional too: without it, the action works on the conversation's most recently created plan (`list` ignores it). |
143+| `tasks` | list of `{description, id?}` | For `create`: the tasks, in order. A task without an `id` gets its position (1, 2, …). Ignored by the other actions. |
144+| `taskId` | integer | For `update`: the task, from 1. |
145+| `status` | string | For `update`: `open`, `in_progress`, `done` or `failed`. |
146+| `message` | string | For `update`: a note kept on the task. |
147+
148+| Action | Does | Result (JSON text) |
149+|--------|------|--------------------|
150+| `create` | Creates the plan, every task `open`. Refused when the name is taken. | `result` (`Plan '<name>' created with N tasks. Current: Task 1`), `planName`, `status`, `current`, `total`; in the line mode, also the whole `plan`. |
151+| `get` | Reads one plan. | `result`, `plan` (name, tasks with id, description, status, start and end times, note). |
152+| `update` | Sets one task's status; `in_progress` records its start time (once) and makes it the current task, `done` and `failed` record its end time. | `result`, `status` (the status line, below). |
153+| `next` | Marks the current task `done` and starts the next one that is neither done nor failed (`in_progress`); a task already finished with `update` is skipped. | `result` (`Task N done. Next: Task M - <description>`, or `Plan '<name>' is complete! All N tasks done.`), `status`, `nextTask` when there is one. |
154+| `list` | Lists the plans held. | `result`, `plans` (names, oldest first). |
155+| `delete` | Removes a plan. | `result`. |
156+| `status` | Reads the status line. | `result` (`Plan '<name>': D/N tasks complete`), `status`. |
157+
158+| Aspect | Behaviour |
159+|--------|-----------|
160+| Status line | `[C/N] <icon> Task C: <description> (<status>)` for the task being worked on — the first one in progress, else the first one still open —, `[N/N] Plan finished: D done, F failed` when every task is over and some failed, `[N/N] All tasks complete!` when all are done. Icons: ⏳ open, 🔄 in progress, ✅ done, ❌ failed. |
161+| Errors | Returned as text, never as a failed generation: `[error: no plans found for this session]`, `[error: no plan named '<name>' found]`, `[error: plan '<name>' already exists]`, `[error: no task with id N in plan '<name>']`, `[error: unknown action '<action>'. Valid actions: …]`. An `update` without a valid `status` quotes the call back, says that `tasks` is ignored by `update` when it was sent, and gives a call that works. |
162+| Storage | In memory, per conversation: the line mode and the window hold one; over ACP each session has its own, and one thread never sees another's plans. Plans are not written to the session file and are lost when mm stops. A conversation that changes starts with no plan: `/new` and `/resume` in every front end, `session/load` and `session/resume` over ACP. |
163+| Permission | `create`, `update`, `next` and `delete` are requested when `yolo: false`; a refusal returns `[plan: command rejected by the user]`. `get`, `list` and `status` never ask. |
164+| Display | Line mode: `📋 plan: <action> <name>` before the call, then `📋 <status line>` after every action that has one (`create`, `update`, `next`, `status`). Window: the call as a tool call, and `📋 <status line>` as a notice. Editor (ACP): the call as a tool call, the status inside its result. The progress is shown by mm, whether or not the model repeats it. |
165+| ACP and window | Tool call of kind `edit` for `create`, `update`, `next`, `delete`, `read` for the others; a result starting with `[error:` is a failed call. |
166+
135167 ## `lsp`
136168
137169 Declared when `lsp.servers` lists at least one server whose command is on the `PATH` at start-up. A listed server that is not found is skipped with a `[warning: lsp server <name> (<command>) not found on the PATH — skipped]`; when none is found the tool is not declared and the banner says so.
@@ -132,6 +132,38 @@ the closest the file comes to it — copy these lines:
132 │ 6 │ }132 │ 6 │ }
133 ```133 ```
134 134
135+## `plan`
136+
137+Declared when `planTool: true` (the default). Turns a list of tasks into a plan the model updates as it works. See [Manage plans and tasks](../how-to/manage-plans-and-tasks.md).
138+
139+| Field | Type | Description |
140+|-------|------|-------------|
141+| `action` | string | Required. `create`, `get`, `update`, `next`, `list`, `delete` or `status`. |
142+| `name` | string | The plan. For `create`, optional: a plan without a name is called `user-request-N` (N counts the conversation's plans). For the other actions, optional too: without it, the action works on the conversation's most recently created plan (`list` ignores it). |
143+| `tasks` | list of `{description, id?}` | For `create`: the tasks, in order. A task without an `id` gets its position (1, 2, …). Ignored by the other actions. |
144+| `taskId` | integer | For `update`: the task, from 1. |
145+| `status` | string | For `update`: `open`, `in_progress`, `done` or `failed`. |
146+| `message` | string | For `update`: a note kept on the task. |
147+
148+| Action | Does | Result (JSON text) |
149+|--------|------|--------------------|
150+| `create` | Creates the plan, every task `open`. Refused when the name is taken. | `result` (`Plan '<name>' created with N tasks. Current: Task 1`), `planName`, `status`, `current`, `total`; in the line mode, also the whole `plan`. |
151+| `get` | Reads one plan. | `result`, `plan` (name, tasks with id, description, status, start and end times, note). |
152+| `update` | Sets one task's status; `in_progress` records its start time (once) and makes it the current task, `done` and `failed` record its end time. | `result`, `status` (the status line, below). |
153+| `next` | Marks the current task `done` and starts the next one that is neither done nor failed (`in_progress`); a task already finished with `update` is skipped. | `result` (`Task N done. Next: Task M - <description>`, or `Plan '<name>' is complete! All N tasks done.`), `status`, `nextTask` when there is one. |
154+| `list` | Lists the plans held. | `result`, `plans` (names, oldest first). |
155+| `delete` | Removes a plan. | `result`. |
156+| `status` | Reads the status line. | `result` (`Plan '<name>': D/N tasks complete`), `status`. |
157+
158+| Aspect | Behaviour |
159+|--------|-----------|
160+| Status line | `[C/N] <icon> Task C: <description> (<status>)` for the task being worked on — the first one in progress, else the first one still open —, `[N/N] Plan finished: D done, F failed` when every task is over and some failed, `[N/N] All tasks complete!` when all are done. Icons: ⏳ open, 🔄 in progress, ✅ done, ❌ failed. |
161+| Errors | Returned as text, never as a failed generation: `[error: no plans found for this session]`, `[error: no plan named '<name>' found]`, `[error: plan '<name>' already exists]`, `[error: no task with id N in plan '<name>']`, `[error: unknown action '<action>'. Valid actions: …]`. An `update` without a valid `status` quotes the call back, says that `tasks` is ignored by `update` when it was sent, and gives a call that works. |
162+| Storage | In memory, per conversation: the line mode and the window hold one; over ACP each session has its own, and one thread never sees another's plans. Plans are not written to the session file and are lost when mm stops. A conversation that changes starts with no plan: `/new` and `/resume` in every front end, `session/load` and `session/resume` over ACP. |
163+| Permission | `create`, `update`, `next` and `delete` are requested when `yolo: false`; a refusal returns `[plan: command rejected by the user]`. `get`, `list` and `status` never ask. |
164+| Display | Line mode: `📋 plan: <action> <name>` before the call, then `📋 <status line>` after every action that has one (`create`, `update`, `next`, `status`). Window: the call as a tool call, and `📋 <status line>` as a notice. Editor (ACP): the call as a tool call, the status inside its result. The progress is shown by mm, whether or not the model repeats it. |
165+| ACP and window | Tool call of kind `edit` for `create`, `update`, `next`, `delete`, `read` for the others; a result starting with `[error:` is a failed call. |
166+
135 ## `lsp`167 ## `lsp`
136 168
137 Declared when `lsp.servers` lists at least one server whose command is on the `PATH` at start-up. A listed server that is not found is skipped with a `[warning: lsp server <name> (<command>) not found on the PATH — skipped]`; when none is found the tool is not declared and the banner says so.169 Declared when `lsp.servers` lists at least one server whose command is on the `PATH` at start-up. A listed server that is not found is skipped with a `[warning: lsp server <name> (<command>) not found on the PATH — skipped]`; when none is found the tool is not declared and the banner says so.
modified docs/fr/README.md +2 -1
@@ -26,6 +26,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
2626 - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le kit `mini-me` de [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), les mixins `go-toolchain` et `cli-tools` sous `sbx/`, mode fenêtre et éditeur
2727 - [Ajouter un skill](how-to/add-a-skill.md)
2828 - [Déclarer un outil personnalisé](how-to/declare-a-custom-tool.md)
29+- [Gérer des plans et des tâches](how-to/manage-plans-and-tasks.md) — l'outil `plan` : donner une liste, suivre la ligne de progression `📋`, des plans par conversation
2930 - [Sauvegarder une réponse dans un fichier](how-to/save-an-answer.md)
3031 - [Chercher dans un dossier de documentation](how-to/search-the-docs.md) — `/docs <mots>`, l'outil `search_docs`, `docSearch` (BM25, sans modèle)
3132 - [Chercher dans les sessions précédentes](how-to/search-earlier-sessions.md) — `/memory <mots>`, l'outil `search_memory`, `sessions.searchMemory` / `sessions.autoSearchMemory`
@@ -54,7 +55,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
5455 - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage
5556 - [Commandes slash](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, raccourcis clavier, touches de la fenêtre
5657 - [Fichier de configuration](reference/configuration.md) — chaque clé YAML, sa valeur par défaut et sa règle de validation
57-- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, outils déclarés
58+- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `plan`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, `search_docs`, `search_memory`, outils déclarés
5859 - [Format d'un fichier de skill](reference/skill-format.md)
5960 - [Les hooks](reference/hooks.md) — les six événements, le JSON qu'un hook lit et écrit, `matcher`, `timeout`, `onError`, erreurs de validation
6061 - [Surface Agent Client Protocol](reference/acp.md)
@@ -26,6 +26,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
26 - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le kit `mini-me` de [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), les mixins `go-toolchain` et `cli-tools` sous `sbx/`, mode fenêtre et éditeur26 - [Faire tourner mini-me dans un Docker Sandbox](how-to/run-in-a-docker-sandbox.md) — le kit `mini-me` de [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit), les mixins `go-toolchain` et `cli-tools` sous `sbx/`, mode fenêtre et éditeur
27 - [Ajouter un skill](how-to/add-a-skill.md)27 - [Ajouter un skill](how-to/add-a-skill.md)
28 - [Déclarer un outil personnalisé](how-to/declare-a-custom-tool.md)28 - [Déclarer un outil personnalisé](how-to/declare-a-custom-tool.md)
29+- [Gérer des plans et des tâches](how-to/manage-plans-and-tasks.md) — l'outil `plan` : donner une liste, suivre la ligne de progression `📋`, des plans par conversation
29 - [Sauvegarder une réponse dans un fichier](how-to/save-an-answer.md)30 - [Sauvegarder une réponse dans un fichier](how-to/save-an-answer.md)
30 - [Chercher dans un dossier de documentation](how-to/search-the-docs.md) — `/docs <mots>`, l'outil `search_docs`, `docSearch` (BM25, sans modèle)31 - [Chercher dans un dossier de documentation](how-to/search-the-docs.md) — `/docs <mots>`, l'outil `search_docs`, `docSearch` (BM25, sans modèle)
31 - [Chercher dans les sessions précédentes](how-to/search-earlier-sessions.md) — `/memory <mots>`, l'outil `search_memory`, `sessions.searchMemory` / `sessions.autoSearchMemory`32 - [Chercher dans les sessions précédentes](how-to/search-earlier-sessions.md) — `/memory <mots>`, l'outil `search_memory`, `sessions.searchMemory` / `sessions.autoSearchMemory`
@@ -54,7 +55,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
54 - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage55 - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage
55 - [Commandes slash](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, raccourcis clavier, touches de la fenêtre56 - [Commandes slash](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, raccourcis clavier, touches de la fenêtre
56 - [Fichier de configuration](reference/configuration.md) — chaque clé YAML, sa valeur par défaut et sa règle de validation57 - [Fichier de configuration](reference/configuration.md) — chaque clé YAML, sa valeur par défaut et sa règle de validation
57-- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, outils déclarés58+- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `plan`, `lsp`, `fetch_content`, `ask_agent`, `ask_user`, `save_answer`, `unload_model`, `search_docs`, `search_memory`, outils déclarés
58 - [Format d'un fichier de skill](reference/skill-format.md)59 - [Format d'un fichier de skill](reference/skill-format.md)
59 - [Les hooks](reference/hooks.md) — les six événements, le JSON qu'un hook lit et écrit, `matcher`, `timeout`, `onError`, erreurs de validation60 - [Les hooks](reference/hooks.md) — les six événements, le JSON qu'un hook lit et écrit, `matcher`, `timeout`, `onError`, erreurs de validation
60 - [Surface Agent Client Protocol](reference/acp.md)61 - [Surface Agent Client Protocol](reference/acp.md)
modified docs/fr/how-to/manage-plans-and-tasks.md +42 -427
@@ -1,450 +1,65 @@
1----
2-title: Gérer les plans et les tâches
3----
1+# Gérer des plans et des tâches
42
5-# Gérer les plans et les tâches
3+Ce guide montre comment faire travailler l'agent sur une liste de tâches avec son outil `plan`, et comment suivre sa progression. L'outil est activé par défaut (`planTool: true`).
64
7-Mini-me inclut un outil intégré **plan** qui permet à l'agent de créer, suivre et gérer des listes de tâches multi-étapes. Lorsque vous donnez à l'agent une liste de tâches à effectuer, il crée automatiquement un plan et suit la progression, en vous montrant l'état actuel après chaque réponse.
5+## Donner une liste à l'agent
86
9-## Exemple rapide
7+Demandez plusieurs choses à la fois, sous forme de liste :
108
9+```text
10+Do these 2 things: 1. create a file a.txt containing A, 2. create a file b.txt containing B.
1111 ```
12-Vous : Fais ces 3 choses : 1. Créer un fichier README, 2. Ajouter une licence, 3. Valider les modifications
1312
14-Agent : Je vais m'en occuper en 3 étapes. En commençant par : Créer un fichier README
15-📋 plan: create user-request-1
13+Le modèle crée un plan à partir de la liste, le suit, et met à jour le statut de chaque tâche au fur et à mesure. Les petits modèles le font plus sûrement quand on le leur dit : « Use the plan tool. Do these… ».
1614
17-Agent : Fichier README créé.
18-📋 [2/3] 🔄 Tâche 2 : Ajouter une licence (in_progress)
15+## Suivre la progression
1916
20-Agent : Licence ajoutée.
21-📋 [3/3] 🔄 Tâche 3 : Valider les modifications (in_progress)
17+mm affiche où en est le plan après chaque action qui change ou lit son état (`create`, `update`, `next`, `status`) — que le modèle en parle ou non dans sa réponse.
2218
23-Agent : Toutes les modifications ont été validées.
24-📋 [3/3] ✅ Toutes les tâches sont terminées !
25-```
26-
27-La ligne `📋` vous montre exactement où se trouve l'agent dans votre plan.
28-
29----
30-
31-## Fonctionnement
32-
33-### Cycle de vie d'un plan
34-
35-1. **Vous formulez votre demande** sous forme de liste de tâches
36-2. **L'agent détecte le plan** et le crée en utilisant l'outil `plan`
37-3. **L'agent travaille sur les tâches** une par une, en mettant à jour l'état
38-4. **L'agent signale la progression** après chaque action
39-5. **Le plan est terminé** lorsque toutes les tâches sont terminées
40-
41-### Icônes d'état
42-
43-| Icône | Signification |
44-|-------|--------------|
45-| ⏳ | Tâche non démarrée (ouverte) |
46-| 🔄 | Tâche en cours |
47-| ✅ | Tâche terminée avec succès |
48-| ❌ | Tâche en échec |
49-
50-### Format de la ligne d'état
51-
52-```
53-📋 [courante/total] icône Tâche N : description (état)
54-```
55-
56-Exemples :
57-- `📋 [1/5] ⏳ Tâche 1 : Analyser le code (open)` - Première tâche, non démarrée
58-- `📋 [2/5] 🔄 Tâche 2 : Corriger les bugs (in_progress)` - Deuxième tâche, en cours
59-- `📋 [5/5] ✅ Toutes les tâches sont terminées !` - Tout est terminé !
60-- `📋 [3/5] ❌ Tâche 3 : Exécuter les tests (failed: timeout de connexion)` - Tâche en échec avec erreur
61-
62----
63-
64-## Scénarios utilisateur
65-
66-### Scénario 1 : Liste numérotée
67-
68-**Votre entrée :**
69-```
70-Fais ces 5 choses :
71-1. Vérifier la version actuelle
72-2. Mettre à jour les dépendances
73-3. Exécuter les tests
74-4. Corriger les échecs
75-5. Déployer en staging
76-```
77-
78-**Ce qui se passe :**
79-- L'agent crée un plan avec 5 tâches
80-- Travaille sur chaque tâche séquentiellement
81-- Affiche la progression : `📋 [1/5]`, `📋 [2/5]`, etc.
82-- Termine avec : `📋 [5/5] ✅ Toutes les tâches sont terminées !`
83-
84----
85-
86-### Scénario 2 : Tâches séparées par des virgules
87-
88-**Votre entrée :**
89-```
90-Configurer le projet : créer le répertoire, initialiser git, et ajouter un README
91-```
92-
93-**Ce qui se passe :**
94-- L'agent détecte qu'il s'agit d'une demande multi-étapes
95-- Crée un plan avec 3 tâches
96-- Exécute dans l'ordre
97-
98----
99-
100-### Scénario 3 : Plusieurs plans
101-
102-Vous pouvez avoir plusieurs plans actifs dans une seule session.
103-
104-**Votre entrée :**
105-```
106-D'abord, corrige le module d'authentification : 1. Trouver le bug, 2. Écrire la correction, 3. Le tester
107-```
108-
109-L'agent travaille sur le plan d'authentification, affiche : `📋 [2/3] 🔄 Tâche 2 : Écrire la correction (in_progress)`
110-
111-**Ensuite vous dites :**
112-```
113-Aussi, mettre à jour la documentation : 1. Mettre à jour le README, 2. Mettre à jour le CHANGELOG
114-```
115-
116-L'agent crée un deuxième plan et gère les deux indépendamment.
117-
118-**Pour voir tous les plans actifs :**
119-```
120-Quels plans ai-je ?
121-```
122-
123----
124-
125-## Interagir avec les plans
126-
127-### Vérifier l'état actuel du plan
128-
129-**Demandez :** "Quel est l'état de mon plan ?"
130-
131-**L'agent répond :**
132-```
133-Plan actuel : [2/3] 🔄 Tâche 2 : Écrire la correction (in_progress). Suivant : Le tester
134-```
135-
136-### Sauter une tâche
137-
138-**Demandez :** "Saute l'étape de test, déploie directement"
139-
140-**Agent :**
141-- Marque la tâche actuelle comme terminée
142-- Saute à la tâche suivante
143-- Confirme : "Je saute l'étape de test. Déploiement en staging..."
144-
145-### Abandonner un plan
146-
147-**Demandez :** "Peu importe, je n'en ai plus besoin"
148-
149-**Agent :**
150-- Demande confirmation
151-- Si vous confirmez, supprime le plan
152-- Toute la progression est perdue
153-
154----
155-
156-## Commandes de gestion des plans
157-
158-Bien que l'outil plan soit principalement piloté par le modèle (l'agent l'utilise automatiquement), vous pouvez également interagir directement avec les plans :
159-
160-### Lister tous les plans
161-
162-**Commande :** `/plans` (pas encore implémenté en v1 - arrive en v2)
163-
164-**Ce que cela affiche :**
165-```
166-Plans actifs :
167-1. configuration-projet (2/5 tâches terminées)
168-2. corriger-bugs (0/3 tâches terminées)
169-```
170-
171-### Voir les détails d'un plan
172-
173-**Demandez à l'agent :** "Montre-moi les détails du plan X"
174-
175-**L'agent répond :** Liste complète des tâches avec l'état pour chaque tâche
176-
177-### Supprimer un plan
178-
179-**Demandez à l'agent :** "Supprime le plan X"
180-
181-**Agent :** Confirme et supprime le plan
182-
183----
184-
185-## Cycle de vie de la session
186-
187-### Plans et commande /new
188-
189-Lorsque vous démarrez une nouvelle session avec `/new` :
190-- Tous les plans actifs sont **supprimés**
191-- C'est intentionnel : une nouvelle session signifie un nouveau départ
192-- Si vous voulez continuer un ancien plan, ne pas utiliser `/new`
193-
194-### Plans et redémarrage de l'agent
195-
196-En v1 (version actuelle) :
197-- Les plans sont stockés **uniquement en mémoire**
198-- Les plans sont **perdus** lorsque l'agent redémarre
199-- Cela garde la v1 simple et rapide
200-
201-En v2 (future) :
202-- Les plans seront persistés dans les fichiers de session
203-- Les plans survivront aux redémarrages de l'agent
204-
205----
206-
207-## L'outil plan (pour les utilisateurs avancés)
208-
209-L'outil `plan` est disponible pour que le modèle puisse l'appeler. Il prend en charge ces actions :
210-
211-| Action | Objectif | Paramètres requis |
212-|--------|----------|-------------------|
213-| `create` | Créer un nouveau plan | `tasks` (tableau de `{description}`) |
214-| `get` | Obtenir les détails d'un plan | `name` |
215-| `update` | Mettre à jour l'état d'une tâche | `name`, `taskId`, `status` |
216-| `next` | Passer à la tâche suivante | `name` |
217-| `list` | Lister tous les plans | aucun |
218-| `delete` | Supprimer un plan | `name` |
219-| `status` | Obtenir la ligne d'état | `name` |
220-
221-### Valeurs d'état
222-
223-- `open` - Tâche non démarrée
224-- `in_progress` - Tâche actuellement en cours
225-- `done` - Tâche terminée avec succès
226-- `failed` - Tâche en échec
227-
228----
229-
230-## La compétence plan-manager
231-
232-L'agent utilise la compétence `plan-manager` pour savoir **quand et comment** utiliser l'outil plan. Cette compétence inclut :
233-
234-- **Modèles de détection** : Comment reconnaître quand un utilisateur donne une demande multi-étapes
235-- **Directives d'utilisation** : Quand créer des plans, mettre à jour des tâches, passer entre les tâches
236-- **Format d'affichage** : Comment montrer l'état du plan à l'utilisateur
237-- **Gestion des erreurs** : Que faire quand les choses tournent mal
238-
239-Vous n'avez pas besoin de charger manuellement cette compétence - l'agent le fait automatiquement lorsque cela est approprié.
240-
241----
242-
243-## Exemples
244-
245-### Exemple 1 : Workflow de correction de bug
246-
247-```
248-Vous : Corrige ces problèmes dans le module d'authentification :
249- 1. Vérifier les exceptions de pointeur nul
250- 2. Ajouter la validation des entrées
251- 3. Écrire des tests unitaires
252- 4. Mettre à jour la documentation
253-
254-Agent : Je vais m'en occuper en 4 étapes. En commençant par : Vérifier les exceptions de pointeur nul
255-📋 plan: create user-request-1
256-
257-... (l'agent vérifie le code) ...
258-
259-Agent : 3 problèmes de pointeur nul potentiels trouvés.
260-📋 [2/4] 🔄 Tâche 2 : Ajouter la validation des entrées (in_progress)
261-
262-... (l'agent ajoute la validation) ...
263-
264-Agent : Validation des entrées ajoutée à auth.go.
265-📋 [3/4] 🔄 Tâche 3 : Écrire des tests unitaires (in_progress)
266-
267-... (l'agent écrit les tests) ...
268-
269-Agent : Tests écrits et réussis.
270-📋 [4/4] 🔄 Tâche 4 : Mettre à jour la documentation (in_progress)
271-
272-... (l'agent met à jour les docs) ...
273-
274-Agent : Documentation mise à jour avec les nouvelles exigences de validation.
275-📋 [4/4] ✅ Toutes les tâches sont terminées !
276-```
277-
278----
279-
280-### Exemple 2 : Configuration d'un projet
281-
282-```
283-Vous : Configure un nouveau projet Go avec :
284- 1. Initialisation du module
285- 2. Structure des répertoires
286- 3. Fichier main
287- 4. Fichier de test
288-
289-Agent : Je vais configurer cela en 4 étapes. En commençant par : Initialisation du module
290-📋 plan: create user-request-1
291-
292-Agent : Module initialisé avec go mod
293-📋 [2/4] 🔄 Tâche 2 : Structure des répertoires (in_progress)
294-
295-Agent : Répertoires créés (cmd, internal, pkg, tests)
296-📋 [3/4] 🔄 Tâche 3 : Fichier main (in_progress)
297-
298-Agent : Fichier main créé avec la structure de base
299-📋 [4/4] 🔄 Tâche 4 : Fichier de test (in_progress)
300-
301-Agent : Fichier de test créé avec des tests exemples
302-📋 [4/4] ✅ Toutes les tâches sont terminées !
303-```
304-
305----
306-
307-### Exemple 3 : Revue de code
308-
309-```
310-Vous : Revoir cette PR et :
311- 1. Vérifier les problèmes de sécurité
312- 2. Vérifier que les tests passent
313- 3. Revoir la documentation
314- 4. Laisser des commentaires
315-
316-Agent : Je vais revoir cette PR en 4 étapes. En commençant par : Vérifier les problèmes de sécurité
317-📋 plan: create user-request-1
318-
319-Agent : Aucun problème de sécurité trouvé.
320-📋 [2/4] 🔄 Tâche 2 : Vérifier que les tests passent (in_progress)
321-
322-Agent : Tous les tests passent.
323-📋 [3/4] 🔄 Tâche 3 : Revoir la documentation (in_progress)
324-
325-Agent : La documentation a l'air bonne, il faut juste corriger une faute de frappe.
326-📋 [4/4] 🔄 Tâche 4 : Laisser des commentaires (in_progress)
327-
328-Agent : Commentaires laissés sur la PR.
329-📋 [4/4] ✅ Toutes les tâches sont terminées !
330-```
331-
332----
333-
334-### Exemple 4 : Avec un échec de tâche
335-
336-```
337-Vous : Fais ceci :
338- 1. Se connecter à la base de données
339- 2. Exécuter la migration
340- 3. Importer les données
341-
342-Agent : Je vais m'en occuper en 3 étapes. En commençant par : Se connecter à la base de données
343-📋 plan: create user-request-1
344-
345-Agent : Connexion à la base de données établie.
346-📋 [2/3] 🔄 Tâche 2 : Exécuter la migration (in_progress)
347-
348-Agent : Migration terminée.
349-📋 [3/3] 🔄 Tâche 3 : Importer les données (in_progress)
350-
351-Agent : Erreur : timeout de connexion à la source de données
352-📋 plan: update user-request-1
353-
354-Agent : ⚠️ La tâche 3 a échoué : timeout de connexion à la source de données
355-📋 [3/3] ❌ Tâche 3 : Importer les données (failed: connection timeout)
356-
357-Agent : 2 des 3 tâches sont terminées. L'import des données a échoué en raison d'un timeout de connexion. Voulez-vous que je réessaye, ou que je saute cette tâche ?
358-```
359-
360----
361-
362-## Configuration
363-
364-L'outil plan peut être activé ou désactivé via le fichier de configuration YAML :
365-
366-```yaml
367-# Dans votre fichier agent.yaml
368-planTool: true # Activé par défaut
369-```
370-
371-Pour désactiver :
372-```yaml
373-planTool: false
374-```
375-
376-Lorsque désactivé, l'agent n'aura pas accès à l'outil `plan` et ne créera pas automatiquement de plans.
377-
378----
379-
380-## Résolution des problèmes
381-
382-### L'agent ne crée pas de plans
383-
384-Si l'agent ne crée pas de plans pour vos demandes multi-étapes :
385-
386-1. **Rendez votre demande plus claire** - Utilisez des listes numérotées
387- - ❌ "Corrige quelques choses"
388- - ✅ "Fais ceci : 1. Corrige A, 2. Corrige B, 3. Corrige C"
389-
390-2. **Soyez plus explicite** - Utilisez des mots comme "plan", "étapes", "puis"
391- - ✅ "Mon plan est de d'abord faire X, puis faire Y"
392-
393-3. **Vérifiez que la compétence est chargée** - L'agent devrait automatiquement charger la compétence `plan-manager` lorsqu'il détecte une demande de type plan
394-
395-### L'agent appelle `plan update` en boucle
396-
397-`update` change l'état d'une tâche ; il ne peut pas modifier les tâches d'un plan. Un modèle qui envoie un `update` sans `status` valide — souvent avec une liste `tasks`, pour essayer de modifier le plan — reçoit une réponse qui cite son appel, nomme le champ manquant, précise que `tasks` n'est lu que par `create`, et donne un appel valide :
398-
399-```
400-[error: plan update changes the status of ONE task, and "status" is missing. You sent: {…}. "status" must be one of: open, in_progress, done, failed. "tasks" is ignored by update (only create reads it): to change a plan's tasks, delete the plan and create it again. A valid call: {"action":"update","name":"user-request-1","taskId":1,"status":"done"}]
401-```
402-
403-Si un petit modèle répète quand même l'appel, mettez `planTool: false` pour un agent dont le métier n'a pas besoin de plans (écrire un document, répondre à des questions) : le modèle travaille alors sans.
404-
405-### Les plans disparaissent
406-
407-En v1, les plans sont stockés en mémoire et seront perdus lorsque :
408-- Vous démarrez une nouvelle session avec `/new`
409-- L'agent redémarre
410-- Vous fermez et rouvrez le terminal
411-
412-C'est un comportement attendu en v1. La persistance des sessions arrive en v2.
413-
414----
19+- **Mode ligne** (`mm agent.yaml`, `mm -p`) : une ligne avant chaque appel, une ligne après. Mesuré sur Qwen3.5 4B avec la demande ci-dessus :
41520
416-## Bonnes pratiques
21+ ```text
22+ 📋 plan: create
23+ 📋 [1/2] ⏳ Task 1: Create a file a.txt containing A (open)
24+ 💾 write_file: a.txt (1 bytes)
25+ 💾 write_file: b.txt (1 bytes)
26+ 📋 plan: update user-request-1
27+ 📋 [2/2] ⏳ Task 2: Create a file b.txt containing B (open)
28+ 📋 plan: update user-request-1
29+ 📋 [2/2] All tasks complete!
30+ ```
41731
418-1. **Soyez spécifique** - Plus il y a de détails dans vos descriptions de tâches, mieux l'agent peut les exécuter
32+- **Fenêtre** (`mm -tui`) : chaque appel est un appel d'outil dans la conversation, et la ligne `📋 [C/N] …` le suit en notice.
33+- **Éditeur** (`mm -acp`) : chaque appel est un appel d'outil ; la ligne de statut est dans son résultat.
41934
420-2. **Gardez les plans concentrés** - Un plan par demande logique. Ne mélangez pas des tâches sans rapport
35+La ligne de statut montre la tâche en cours de travail — la première en cours, sinon la première encore ouverte : `[C/N] <icône> Task C: <description> (<statut>)`. Icônes : ⏳ ouverte, 🔄 en cours, ✅ faite, ❌ en échec. Quand toutes les tâches sont terminées, elle indique `[N/N] All tasks complete!`, ou `[N/N] Plan finished: D done, F failed` si certaines ont échoué.
42136
422-3. **Vérifiez la progression** - La ligne d'état vous montre exactement où se trouve l'agent
37+## Orienter le plan
42338
424-4. **Demandez des clarifications** - Si l'agent semble bloqué ou confus, demandez "Quel est l'état ?"
39+C'est le modèle qui décide quand appeler l'outil ; vous l'orientez avec vos propres mots :
42540
426-5. **Commencez petit** - Pour un travail multi-étapes complexe, commencez par quelques tâches et ajoutez-en davantage si nécessaire
41+- **Où en est-on ?** « Où en est le plan ? » — le modèle appelle `status` et mm affiche la ligne.
42+- **Sauter une tâche.** « Saute les tests et continue. » — le modèle peut la marquer et passer à la suivante avec `next`.
43+- **Une tâche a échoué.** Le modèle peut la marquer `failed`, avec une note ; la ligne de statut passe alors à la tâche ouverte suivante.
44+- **Abandonner le plan.** « Oublie ce plan. » — le modèle peut appeler `delete`.
42745
428----
46+Il n'y a pas de commande slash pour les plans : c'est l'outil du modèle.
42947
430-## Comparaison avec d'autres outils
48+## Variantes
43149
432-| Fonctionnalité | outil plan | compétences | commandes slash |
433-|---------------|------------|-------------|-----------------|
434-| Objectif | Suivre le travail multi-étapes | Charger des procédures | Commandes utilisateur |
435-| Déclenchement | Automatique (modèle détecte) | Explicite (`/skill`) | Explicite (`/command`) |
436-| Persistance (v1) | En mémoire, session uniquement | Basé sur des fichiers | N/A |
437-| Interaction utilisateur | Indirecte (via le modèle) | Indirecte (modèle charge) | Directe |
50+- **Plusieurs conversations dans un éditeur.** Chaque fil a ses propres plans : un plan créé dans un fil de Zed n'est pas visible dans un autre.
51+- **Repartir de zéro.** `/new` et `/resume` oublient les plans dans toutes les façades ; un rechargement depuis l'éditeur (`session/load`, `session/resume`) démarre le fil sans plan. Les plans ne sont gardés qu'en mémoire, jamais dans le fichier de session, et sont perdus à l'arrêt de mm.
52+- **Le skill `plan-manager`.** `skills/plan-manager` dit au modèle quand faire un plan et comment le parcourir. Mettez-le dans le `skillsDir` de l'agent pour un modèle qui n'utilise pas l'outil de lui-même. Il demande aussi au modèle de reprendre la ligne de statut dans sa réponse ; mm l'affiche de toute façon.
53+- **Un agent qui n'a pas besoin de plans.** Mettez `planTool: false` : l'outil n'est pas déclaré. Faites-le pour un agent dont le travail est une réponse unique (écrire un document, répondre à des questions), où un petit modèle pourrait sinon boucler sur des appels au plan.
43854
439-L'outil `plan` complète les autres fonctionnalités de mini-me :
440-- Utilisez les **compétences** pour des procédures complexes et réutilisables
441-- Utilisez l'**outil plan** pour des demandes multi-étapes ad-hoc
442-- Utilisez les **commandes slash** pour des actions utilisateur directes
55+## Dépannage
44356
444----
57+- **L'agent ne crée pas de plan.** Donnez une liste numérotée, et dites « use the plan tool ». Avec un petit modèle, ajoutez le skill `plan-manager`.
58+- **L'agent appelle `plan update` en boucle.** `update` change le statut d'UNE tâche ; il ne peut pas changer les tâches d'un plan. Un appel sans `status` valide reçoit une réponse qui reprend l'appel, nomme le champ manquant et donne un appel valide. Si un petit modèle le répète quand même, mettez `planTool: false` pour cet agent.
44559
44660 ## Voir aussi
44761
448-- [Référence des commandes slash](../reference/slash-commands.md) - Pour les commandes utilisateur
449-- [Référence des outils](../reference/tools.md) - Pour tous les outils intégrés
450-- [Aperçu des compétences](../explanation/skills.md) - Pour le système de compétences
62+- [Référence des outils — `plan`](../reference/tools.md#plan) : chaque action, champ, résultat et erreur
63+- [Référence de la configuration](../reference/configuration.md) : `planTool`
64+- [Référence des commandes](../reference/slash-commands.md) : `/new`, `/resume`
65+- `demos/plans/` — une démo mesurée : un programme Go construit en quatre étapes planifiées, et un plan qui se termine sur une tâche en échec
@@ -1,450 +1,65 @@
1----1+# Gérer des plans et des tâches
2-title: Gérer les plans et les tâches
3----
4 2
5-# Gérer les plans et les tâches3+Ce guide montre comment faire travailler l'agent sur une liste de tâches avec son outil `plan`, et comment suivre sa progression. L'outil est activé par défaut (`planTool: true`).
6 4
7-Mini-me inclut un outil intégré **plan** qui permet à l'agent de créer, suivre et gérer des listes de tâches multi-étapes. Lorsque vous donnez à l'agent une liste de tâches à effectuer, il crée automatiquement un plan et suit la progression, en vous montrant l'état actuel après chaque réponse.5+## Donner une liste à l'agent
8 6
9-## Exemple rapide7+Demandez plusieurs choses à la fois, sous forme de liste :
10 8
9+```text
10+Do these 2 things: 1. create a file a.txt containing A, 2. create a file b.txt containing B.
11 ```11 ```
12-Vous : Fais ces 3 choses : 1. Créer un fichier README, 2. Ajouter une licence, 3. Valider les modifications
13 12
14-Agent : Je vais m'en occuper en 3 étapes. En commençant par : Créer un fichier README13+Le modèle crée un plan à partir de la liste, le suit, et met à jour le statut de chaque tâche au fur et à mesure. Les petits modèles le font plus sûrement quand on le leur dit : « Use the plan tool. Do these… ».
15-📋 plan: create user-request-1
16 14
17-Agent : Fichier README créé.15+## Suivre la progression
18-📋 [2/3] 🔄 Tâche 2 : Ajouter une licence (in_progress)
19 16
20-Agent : Licence ajoutée.17+mm affiche où en est le plan après chaque action qui change ou lit son état (`create`, `update`, `next`, `status`) — que le modèle en parle ou non dans sa réponse.
21-📋 [3/3] 🔄 Tâche 3 : Valider les modifications (in_progress)
22 18
23-Agent : Toutes les modifications ont été validées.19+- **Mode ligne** (`mm agent.yaml`, `mm -p`) : une ligne avant chaque appel, une ligne après. Mesuré sur Qwen3.5 4B avec la demande ci-dessus :
24-📋 [3/3] ✅ Toutes les tâches sont terminées !
25-```
26-
27-La ligne `📋` vous montre exactement où se trouve l'agent dans votre plan.
28-
29----
30-
31-## Fonctionnement
32-
33-### Cycle de vie d'un plan
34-
35-1. **Vous formulez votre demande** sous forme de liste de tâches
36-2. **L'agent détecte le plan** et le crée en utilisant l'outil `plan`
37-3. **L'agent travaille sur les tâches** une par une, en mettant à jour l'état
38-4. **L'agent signale la progression** après chaque action
39-5. **Le plan est terminé** lorsque toutes les tâches sont terminées
40-
41-### Icônes d'état
42-
43-| Icône | Signification |
44-|-------|--------------|
45-| ⏳ | Tâche non démarrée (ouverte) |
46-| 🔄 | Tâche en cours |
47-| ✅ | Tâche terminée avec succès |
48-| ❌ | Tâche en échec |
49-
50-### Format de la ligne d'état
51-
52-```
53-📋 [courante/total] icône Tâche N : description (état)
54-```
55-
56-Exemples :
57-- `📋 [1/5] ⏳ Tâche 1 : Analyser le code (open)` - Première tâche, non démarrée
58-- `📋 [2/5] 🔄 Tâche 2 : Corriger les bugs (in_progress)` - Deuxième tâche, en cours
59-- `📋 [5/5] ✅ Toutes les tâches sont terminées !` - Tout est terminé !
60-- `📋 [3/5] ❌ Tâche 3 : Exécuter les tests (failed: timeout de connexion)` - Tâche en échec avec erreur
61-
62----
63-
64-## Scénarios utilisateur
65-
66-### Scénario 1 : Liste numérotée
67-
68-**Votre entrée :**
69-```
70-Fais ces 5 choses :
71-1. Vérifier la version actuelle
72-2. Mettre à jour les dépendances
73-3. Exécuter les tests
74-4. Corriger les échecs
75-5. Déployer en staging
76-```
77-
78-**Ce qui se passe :**
79-- L'agent crée un plan avec 5 tâches
80-- Travaille sur chaque tâche séquentiellement
81-- Affiche la progression : `📋 [1/5]`, `📋 [2/5]`, etc.
82-- Termine avec : `📋 [5/5] ✅ Toutes les tâches sont terminées !`
83-
84----
85-
86-### Scénario 2 : Tâches séparées par des virgules
87-
88-**Votre entrée :**
89-```
90-Configurer le projet : créer le répertoire, initialiser git, et ajouter un README
91-```
92-
93-**Ce qui se passe :**
94-- L'agent détecte qu'il s'agit d'une demande multi-étapes
95-- Crée un plan avec 3 tâches
96-- Exécute dans l'ordre
97-
98----
99-
100-### Scénario 3 : Plusieurs plans
101-
102-Vous pouvez avoir plusieurs plans actifs dans une seule session.
103-
104-**Votre entrée :**
105-```
106-D'abord, corrige le module d'authentification : 1. Trouver le bug, 2. Écrire la correction, 3. Le tester
107-```
108-
109-L'agent travaille sur le plan d'authentification, affiche : `📋 [2/3] 🔄 Tâche 2 : Écrire la correction (in_progress)`
110-
111-**Ensuite vous dites :**
112-```
113-Aussi, mettre à jour la documentation : 1. Mettre à jour le README, 2. Mettre à jour le CHANGELOG
114-```
115-
116-L'agent crée un deuxième plan et gère les deux indépendamment.
117-
118-**Pour voir tous les plans actifs :**
119-```
120-Quels plans ai-je ?
121-```
122-
123----
124-
125-## Interagir avec les plans
126-
127-### Vérifier l'état actuel du plan
128-
129-**Demandez :** "Quel est l'état de mon plan ?"
130-
131-**L'agent répond :**
132-```
133-Plan actuel : [2/3] 🔄 Tâche 2 : Écrire la correction (in_progress). Suivant : Le tester
134-```
135-
136-### Sauter une tâche
137-
138-**Demandez :** "Saute l'étape de test, déploie directement"
139-
140-**Agent :**
141-- Marque la tâche actuelle comme terminée
142-- Saute à la tâche suivante
143-- Confirme : "Je saute l'étape de test. Déploiement en staging..."
144-
145-### Abandonner un plan
146-
147-**Demandez :** "Peu importe, je n'en ai plus besoin"
148-
149-**Agent :**
150-- Demande confirmation
151-- Si vous confirmez, supprime le plan
152-- Toute la progression est perdue
153-
154----
155-
156-## Commandes de gestion des plans
157-
158-Bien que l'outil plan soit principalement piloté par le modèle (l'agent l'utilise automatiquement), vous pouvez également interagir directement avec les plans :
159-
160-### Lister tous les plans
161-
162-**Commande :** `/plans` (pas encore implémenté en v1 - arrive en v2)
163-
164-**Ce que cela affiche :**
165-```
166-Plans actifs :
167-1. configuration-projet (2/5 tâches terminées)
168-2. corriger-bugs (0/3 tâches terminées)
169-```
170-
171-### Voir les détails d'un plan
172-
173-**Demandez à l'agent :** "Montre-moi les détails du plan X"
174-
175-**L'agent répond :** Liste complète des tâches avec l'état pour chaque tâche
176-
177-### Supprimer un plan
178-
179-**Demandez à l'agent :** "Supprime le plan X"
180-
181-**Agent :** Confirme et supprime le plan
182-
183----
184-
185-## Cycle de vie de la session
186-
187-### Plans et commande /new
188-
189-Lorsque vous démarrez une nouvelle session avec `/new` :
190-- Tous les plans actifs sont **supprimés**
191-- C'est intentionnel : une nouvelle session signifie un nouveau départ
192-- Si vous voulez continuer un ancien plan, ne pas utiliser `/new`
193-
194-### Plans et redémarrage de l'agent
195-
196-En v1 (version actuelle) :
197-- Les plans sont stockés **uniquement en mémoire**
198-- Les plans sont **perdus** lorsque l'agent redémarre
199-- Cela garde la v1 simple et rapide
200-
201-En v2 (future) :
202-- Les plans seront persistés dans les fichiers de session
203-- Les plans survivront aux redémarrages de l'agent
204-
205----
206-
207-## L'outil plan (pour les utilisateurs avancés)
208-
209-L'outil `plan` est disponible pour que le modèle puisse l'appeler. Il prend en charge ces actions :
210-
211-| Action | Objectif | Paramètres requis |
212-|--------|----------|-------------------|
213-| `create` | Créer un nouveau plan | `tasks` (tableau de `{description}`) |
214-| `get` | Obtenir les détails d'un plan | `name` |
215-| `update` | Mettre à jour l'état d'une tâche | `name`, `taskId`, `status` |
216-| `next` | Passer à la tâche suivante | `name` |
217-| `list` | Lister tous les plans | aucun |
218-| `delete` | Supprimer un plan | `name` |
219-| `status` | Obtenir la ligne d'état | `name` |
220-
221-### Valeurs d'état
222-
223-- `open` - Tâche non démarrée
224-- `in_progress` - Tâche actuellement en cours
225-- `done` - Tâche terminée avec succès
226-- `failed` - Tâche en échec
227-
228----
229-
230-## La compétence plan-manager
231-
232-L'agent utilise la compétence `plan-manager` pour savoir **quand et comment** utiliser l'outil plan. Cette compétence inclut :
233-
234-- **Modèles de détection** : Comment reconnaître quand un utilisateur donne une demande multi-étapes
235-- **Directives d'utilisation** : Quand créer des plans, mettre à jour des tâches, passer entre les tâches
236-- **Format d'affichage** : Comment montrer l'état du plan à l'utilisateur
237-- **Gestion des erreurs** : Que faire quand les choses tournent mal
238-
239-Vous n'avez pas besoin de charger manuellement cette compétence - l'agent le fait automatiquement lorsque cela est approprié.
240-
241----
242-
243-## Exemples
244-
245-### Exemple 1 : Workflow de correction de bug
246-
247-```
248-Vous : Corrige ces problèmes dans le module d'authentification :
249- 1. Vérifier les exceptions de pointeur nul
250- 2. Ajouter la validation des entrées
251- 3. Écrire des tests unitaires
252- 4. Mettre à jour la documentation
253-
254-Agent : Je vais m'en occuper en 4 étapes. En commençant par : Vérifier les exceptions de pointeur nul
255-📋 plan: create user-request-1
256-
257-... (l'agent vérifie le code) ...
258-
259-Agent : 3 problèmes de pointeur nul potentiels trouvés.
260-📋 [2/4] 🔄 Tâche 2 : Ajouter la validation des entrées (in_progress)
261-
262-... (l'agent ajoute la validation) ...
263-
264-Agent : Validation des entrées ajoutée à auth.go.
265-📋 [3/4] 🔄 Tâche 3 : Écrire des tests unitaires (in_progress)
266-
267-... (l'agent écrit les tests) ...
268-
269-Agent : Tests écrits et réussis.
270-📋 [4/4] 🔄 Tâche 4 : Mettre à jour la documentation (in_progress)
271-
272-... (l'agent met à jour les docs) ...
273-
274-Agent : Documentation mise à jour avec les nouvelles exigences de validation.
275-📋 [4/4] ✅ Toutes les tâches sont terminées !
276-```
277-
278----
279-
280-### Exemple 2 : Configuration d'un projet
281-
282-```
283-Vous : Configure un nouveau projet Go avec :
284- 1. Initialisation du module
285- 2. Structure des répertoires
286- 3. Fichier main
287- 4. Fichier de test
288-
289-Agent : Je vais configurer cela en 4 étapes. En commençant par : Initialisation du module
290-📋 plan: create user-request-1
291-
292-Agent : Module initialisé avec go mod
293-📋 [2/4] 🔄 Tâche 2 : Structure des répertoires (in_progress)
294-
295-Agent : Répertoires créés (cmd, internal, pkg, tests)
296-📋 [3/4] 🔄 Tâche 3 : Fichier main (in_progress)
297-
298-Agent : Fichier main créé avec la structure de base
299-📋 [4/4] 🔄 Tâche 4 : Fichier de test (in_progress)
300-
301-Agent : Fichier de test créé avec des tests exemples
302-📋 [4/4] ✅ Toutes les tâches sont terminées !
303-```
304-
305----
306-
307-### Exemple 3 : Revue de code
308-
309-```
310-Vous : Revoir cette PR et :
311- 1. Vérifier les problèmes de sécurité
312- 2. Vérifier que les tests passent
313- 3. Revoir la documentation
314- 4. Laisser des commentaires
315-
316-Agent : Je vais revoir cette PR en 4 étapes. En commençant par : Vérifier les problèmes de sécurité
317-📋 plan: create user-request-1
318-
319-Agent : Aucun problème de sécurité trouvé.
320-📋 [2/4] 🔄 Tâche 2 : Vérifier que les tests passent (in_progress)
321-
322-Agent : Tous les tests passent.
323-📋 [3/4] 🔄 Tâche 3 : Revoir la documentation (in_progress)
324-
325-Agent : La documentation a l'air bonne, il faut juste corriger une faute de frappe.
326-📋 [4/4] 🔄 Tâche 4 : Laisser des commentaires (in_progress)
327-
328-Agent : Commentaires laissés sur la PR.
329-📋 [4/4] ✅ Toutes les tâches sont terminées !
330-```
331-
332----
333-
334-### Exemple 4 : Avec un échec de tâche
335-
336-```
337-Vous : Fais ceci :
338- 1. Se connecter à la base de données
339- 2. Exécuter la migration
340- 3. Importer les données
341-
342-Agent : Je vais m'en occuper en 3 étapes. En commençant par : Se connecter à la base de données
343-📋 plan: create user-request-1
344-
345-Agent : Connexion à la base de données établie.
346-📋 [2/3] 🔄 Tâche 2 : Exécuter la migration (in_progress)
347-
348-Agent : Migration terminée.
349-📋 [3/3] 🔄 Tâche 3 : Importer les données (in_progress)
350-
351-Agent : Erreur : timeout de connexion à la source de données
352-📋 plan: update user-request-1
353-
354-Agent : ⚠️ La tâche 3 a échoué : timeout de connexion à la source de données
355-📋 [3/3] ❌ Tâche 3 : Importer les données (failed: connection timeout)
356-
357-Agent : 2 des 3 tâches sont terminées. L'import des données a échoué en raison d'un timeout de connexion. Voulez-vous que je réessaye, ou que je saute cette tâche ?
358-```
359-
360----
361-
362-## Configuration
363-
364-L'outil plan peut être activé ou désactivé via le fichier de configuration YAML :
365-
366-```yaml
367-# Dans votre fichier agent.yaml
368-planTool: true # Activé par défaut
369-```
370-
371-Pour désactiver :
372-```yaml
373-planTool: false
374-```
375-
376-Lorsque désactivé, l'agent n'aura pas accès à l'outil `plan` et ne créera pas automatiquement de plans.
377-
378----
379-
380-## Résolution des problèmes
381-
382-### L'agent ne crée pas de plans
383-
384-Si l'agent ne crée pas de plans pour vos demandes multi-étapes :
385-
386-1. **Rendez votre demande plus claire** - Utilisez des listes numérotées
387- - ❌ "Corrige quelques choses"
388- - ✅ "Fais ceci : 1. Corrige A, 2. Corrige B, 3. Corrige C"
389-
390-2. **Soyez plus explicite** - Utilisez des mots comme "plan", "étapes", "puis"
391- - ✅ "Mon plan est de d'abord faire X, puis faire Y"
392-
393-3. **Vérifiez que la compétence est chargée** - L'agent devrait automatiquement charger la compétence `plan-manager` lorsqu'il détecte une demande de type plan
394-
395-### L'agent appelle `plan update` en boucle
396-
397-`update` change l'état d'une tâche ; il ne peut pas modifier les tâches d'un plan. Un modèle qui envoie un `update` sans `status` valide — souvent avec une liste `tasks`, pour essayer de modifier le plan — reçoit une réponse qui cite son appel, nomme le champ manquant, précise que `tasks` n'est lu que par `create`, et donne un appel valide :
398-
399-```
400-[error: plan update changes the status of ONE task, and "status" is missing. You sent: {…}. "status" must be one of: open, in_progress, done, failed. "tasks" is ignored by update (only create reads it): to change a plan's tasks, delete the plan and create it again. A valid call: {"action":"update","name":"user-request-1","taskId":1,"status":"done"}]
401-```
402-
403-Si un petit modèle répète quand même l'appel, mettez `planTool: false` pour un agent dont le métier n'a pas besoin de plans (écrire un document, répondre à des questions) : le modèle travaille alors sans.
404-
405-### Les plans disparaissent
406-
407-En v1, les plans sont stockés en mémoire et seront perdus lorsque :
408-- Vous démarrez une nouvelle session avec `/new`
409-- L'agent redémarre
410-- Vous fermez et rouvrez le terminal
411-
412-C'est un comportement attendu en v1. La persistance des sessions arrive en v2.
413-
414----
415 20
416-## Bonnes pratiques21+ ```text
22+ 📋 plan: create
23+ 📋 [1/2] ⏳ Task 1: Create a file a.txt containing A (open)
24+ 💾 write_file: a.txt (1 bytes)
25+ 💾 write_file: b.txt (1 bytes)
26+ 📋 plan: update user-request-1
27+ 📋 [2/2] ⏳ Task 2: Create a file b.txt containing B (open)
28+ 📋 plan: update user-request-1
29+ 📋 [2/2] All tasks complete!
30+ ```
417 31
418-1. **Soyez spécifique** - Plus il y a de détails dans vos descriptions de tâches, mieux l'agent peut les exécuter32+- **Fenêtre** (`mm -tui`) : chaque appel est un appel d'outil dans la conversation, et la ligne `📋 [C/N] …` le suit en notice.
33+- **Éditeur** (`mm -acp`) : chaque appel est un appel d'outil ; la ligne de statut est dans son résultat.
419 34
420-2. **Gardez les plans concentrés** - Un plan par demande logique. Ne mélangez pas des tâches sans rapport35+La ligne de statut montre la tâche en cours de travail — la première en cours, sinon la première encore ouverte : `[C/N] <icône> Task C: <description> (<statut>)`. Icônes : ⏳ ouverte, 🔄 en cours, ✅ faite, ❌ en échec. Quand toutes les tâches sont terminées, elle indique `[N/N] All tasks complete!`, ou `[N/N] Plan finished: D done, F failed` si certaines ont échoué.
421 36
422-3. **Vérifiez la progression** - La ligne d'état vous montre exactement où se trouve l'agent37+## Orienter le plan
423 38
424-4. **Demandez des clarifications** - Si l'agent semble bloqué ou confus, demandez "Quel est l'état ?"39+C'est le modèle qui décide quand appeler l'outil ; vous l'orientez avec vos propres mots :
425 40
426-5. **Commencez petit** - Pour un travail multi-étapes complexe, commencez par quelques tâches et ajoutez-en davantage si nécessaire41+- **Où en est-on ?** « Où en est le plan ? » — le modèle appelle `status` et mm affiche la ligne.
42+- **Sauter une tâche.** « Saute les tests et continue. » — le modèle peut la marquer et passer à la suivante avec `next`.
43+- **Une tâche a échoué.** Le modèle peut la marquer `failed`, avec une note ; la ligne de statut passe alors à la tâche ouverte suivante.
44+- **Abandonner le plan.** « Oublie ce plan. » — le modèle peut appeler `delete`.
427 45
428----46+Il n'y a pas de commande slash pour les plans : c'est l'outil du modèle.
429 47
430-## Comparaison avec d'autres outils48+## Variantes
431 49
432-| Fonctionnalité | outil plan | compétences | commandes slash |50+- **Plusieurs conversations dans un éditeur.** Chaque fil a ses propres plans : un plan créé dans un fil de Zed n'est pas visible dans un autre.
433-|---------------|------------|-------------|-----------------|51+- **Repartir de zéro.** `/new` et `/resume` oublient les plans dans toutes les façades ; un rechargement depuis l'éditeur (`session/load`, `session/resume`) démarre le fil sans plan. Les plans ne sont gardés qu'en mémoire, jamais dans le fichier de session, et sont perdus à l'arrêt de mm.
434-| Objectif | Suivre le travail multi-étapes | Charger des procédures | Commandes utilisateur |52+- **Le skill `plan-manager`.** `skills/plan-manager` dit au modèle quand faire un plan et comment le parcourir. Mettez-le dans le `skillsDir` de l'agent pour un modèle qui n'utilise pas l'outil de lui-même. Il demande aussi au modèle de reprendre la ligne de statut dans sa réponse ; mm l'affiche de toute façon.
435-| Déclenchement | Automatique (modèle détecte) | Explicite (`/skill`) | Explicite (`/command`) |53+- **Un agent qui n'a pas besoin de plans.** Mettez `planTool: false` : l'outil n'est pas déclaré. Faites-le pour un agent dont le travail est une réponse unique (écrire un document, répondre à des questions), où un petit modèle pourrait sinon boucler sur des appels au plan.
436-| Persistance (v1) | En mémoire, session uniquement | Basé sur des fichiers | N/A |
437-| Interaction utilisateur | Indirecte (via le modèle) | Indirecte (modèle charge) | Directe |
438 54
439-L'outil `plan` complète les autres fonctionnalités de mini-me :55+## Dépannage
440-- Utilisez les **compétences** pour des procédures complexes et réutilisables
441-- Utilisez l'**outil plan** pour des demandes multi-étapes ad-hoc
442-- Utilisez les **commandes slash** pour des actions utilisateur directes
443 56
444----57+- **L'agent ne crée pas de plan.** Donnez une liste numérotée, et dites « use the plan tool ». Avec un petit modèle, ajoutez le skill `plan-manager`.
58+- **L'agent appelle `plan update` en boucle.** `update` change le statut d'UNE tâche ; il ne peut pas changer les tâches d'un plan. Un appel sans `status` valide reçoit une réponse qui reprend l'appel, nomme le champ manquant et donne un appel valide. Si un petit modèle le répète quand même, mettez `planTool: false` pour cet agent.
445 59
446 ## Voir aussi60 ## Voir aussi
447 61
448-- [Référence des commandes slash](../reference/slash-commands.md) - Pour les commandes utilisateur62+- [Référence des outils — `plan`](../reference/tools.md#plan) : chaque action, champ, résultat et erreur
449-- [Référence des outils](../reference/tools.md) - Pour tous les outils intégrés63+- [Référence de la configuration](../reference/configuration.md) : `planTool`
450-- [Aperçu des compétences](../explanation/skills.md) - Pour le système de compétences64+- [Référence des commandes](../reference/slash-commands.md) : `/new`, `/resume`
65+- `demos/plans/` — une démo mesurée : un programme Go construit en quatre étapes planifiées, et un plan qui se termine sur une tâche en échec
modified docs/fr/reference/slash-commands.md +3 -3
@@ -9,9 +9,9 @@
99 | `/quit` | Quitte le programme. | Annule la génération, affiche `Goodbye.`, quitte. |
1010 | `/abort` | Affiche `⚠️ No generation in progress.` | Annule la génération, affiche `🛑 Aborting...` ; les commandes déjà exécutées restent dans l'historique. |
1111 | `/compact` | Compresse l'historique immédiatement, sans tenir compte du seuil ; affiche le rapport `🗜️`, ou `🗜️ nothing to compact` quand aucun tour n'est plus ancien que `context.keepLastTurns`. | Envoyée au modèle comme nouveau message (voir ci-dessous). |
12-| `/new` | Ramène l'historique au seul prompt système, réinitialise le détecteur de boucles et oublie le dernier compte de tokens du serveur ; affiche `🆕 new session: N message(s) forgotten` (N compte tous les messages sauf le prompt système). | Traitée comme n'importe quel texte : annule la génération, conserve son historique partiel, puis s'exécute comme `/new` sur cet historique. |
12+| `/new` | Ramène l'historique au seul prompt système, réinitialise le détecteur de boucles, oublie le dernier compte de tokens du serveur et les plans de la conversation ; affiche `🆕 new session: N message(s) forgotten` (N compte tous les messages sauf le prompt système). | Traitée comme n'importe quel texte : annule la génération, conserve son historique partiel, puis s'exécute comme `/new` sur cet historique. |
1313 | `/sessions` | Liste les sessions enregistrées du projet, la plus récente d'abord : identifiant, dernière mise à jour, nombre de questions, première question, puis ` · ⚠️ N error(s)` quand la session a enregistré des erreurs ; `*` marque la session courante. Avec `sessions.enabled: false` : `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Envoyée au modèle comme nouveau message. |
14-| `/resume [id]` | Reprend une session enregistrée : la plus récemment mise à jour autre que la courante, en ignorant les vides, ou l'identifiant donné. Affiche `↩️ resumed <id> — N message(s), started with "<première question>"` (suivi de ` · ⚠️ N error(s)` quand la session a enregistré des erreurs) ; réinitialise le détecteur de boucles et le compte de tokens du serveur ; les tours suivants sont enregistrés dans cette session. Refusée avec une raison `⚠️` quand les sessions sont désactivées, qu'il n'y a pas de session antérieure, que l'identifiant est inconnu ou désigne la session courante. | Envoyée au modèle comme nouveau message. |
14+| `/resume [id]` | Reprend une session enregistrée : la plus récemment mise à jour autre que la courante, en ignorant les vides, ou l'identifiant donné. Affiche `↩️ resumed <id> — N message(s), started with "<première question>"` (suivi de ` · ⚠️ N error(s)` quand la session a enregistré des erreurs) ; réinitialise le détecteur de boucles, le compte de tokens du serveur et les plans ; les tours suivants sont enregistrés dans cette session. Refusée avec une raison `⚠️` quand les sessions sont désactivées, qu'il n'y a pas de session antérieure, que l'identifiant est inconnu ou désigne la session courante. | Envoyée au modèle comme nouveau message. |
1515 | `/help` | Affiche les commandes de cette façade, une par ligne avec leur description, alignées. | Envoyée au modèle comme nouveau message. |
1616 | `/save <fichier> [n]` | Écrit la dernière réponse (`n` : la n-ième depuis la fin) telle quelle dans un nouveau fichier, en créant les répertoires manquants, et affiche `💾 saved the last answer to <fichier> (N bytes)`. N'écrase jamais : un fichier existant donne `⚠️ <fichier> already exists — nothing was written; choose another name`. Un nom de fichier avec des espaces n'est pas pris en charge. Le modèle n'est pas appelé. | Envoyée au modèle comme nouveau message. |
1717 | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory` : cherche dans les sessions enregistrées du projet et affiche `🔎 N hit(s) for "<mots>" in <rép>`, puis deux lignes par résultat — `<id de session> · <date> · turn <n> · question\|answer\|summary` et le texte autour des mots trouvés, plus `→ <réponse>` sous une question (la réponse sur laquelle son tour s'est terminé) — ou `🔎 no hit for "<mots>" in <rép>`. La casse et les accents ne comptent pas ; les mots d'une ou deux lettres sont ignorés à côté de mots plus longs ; un résultat contient au moins un des mots ; les résultats sont classés par nombre de mots contenus, puis du plus récent au plus ancien ; 10 au plus. Les questions, les réponses et les résumés de `/compact` sont cherchés, pas les appels d'outils ; la conversation en cours est exclue. Sans mots : `⚠️ nothing to search: give one or more words`. Le modèle n'est pas appelé ; l'historique n'est pas touché. | Envoyée au modèle comme nouveau message. |
@@ -176,7 +176,7 @@ Deux commandes existent en mode ACP, trois avec `unloadModels` (le défaut) : `/
176176
177177 | Commande | En ACP |
178178 |----------|--------|
179-| `/new` | Comme dans le terminal : historique ramené au prompt système, compte de tokens oublié ; l'identifiant de session, le `cwd` et les autorisations « toujours autoriser » sont conservés. |
179+| `/new` | Comme dans le terminal : historique ramené au prompt système, compte de tokens et plans de cette session oubliés (les autres sessions gardent les leurs) ; l'identifiant de session, le `cwd` et les autorisations « toujours autoriser » sont conservés. |
180180 | `/compact` | Comme dans le terminal, toujours forcée : `🗜️ compressed …`, `🗜️ nothing to compact: …` ou `[compact: failed, history kept: …]`. Attend d'abord la fin d'un tour en cours. |
181181 | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, dans le projet de la session (`sessions.dir` sous son `cwd`), comme texte de l'agent ; l'historique n'est pas touché. |
182182 | `/docs <mots>` | Avec `docSearch.searchDocs`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, depuis le `cwd` de la session, comme texte de l'agent ; l'historique n'est pas touché. |
@@ -9,9 +9,9 @@
9 | `/quit` | Quitte le programme. | Annule la génération, affiche `Goodbye.`, quitte. |9 | `/quit` | Quitte le programme. | Annule la génération, affiche `Goodbye.`, quitte. |
10 | `/abort` | Affiche `⚠️ No generation in progress.` | Annule la génération, affiche `🛑 Aborting...` ; les commandes déjà exécutées restent dans l'historique. |10 | `/abort` | Affiche `⚠️ No generation in progress.` | Annule la génération, affiche `🛑 Aborting...` ; les commandes déjà exécutées restent dans l'historique. |
11 | `/compact` | Compresse l'historique immédiatement, sans tenir compte du seuil ; affiche le rapport `🗜️`, ou `🗜️ nothing to compact` quand aucun tour n'est plus ancien que `context.keepLastTurns`. | Envoyée au modèle comme nouveau message (voir ci-dessous). |11 | `/compact` | Compresse l'historique immédiatement, sans tenir compte du seuil ; affiche le rapport `🗜️`, ou `🗜️ nothing to compact` quand aucun tour n'est plus ancien que `context.keepLastTurns`. | Envoyée au modèle comme nouveau message (voir ci-dessous). |
12-| `/new` | Ramène l'historique au seul prompt système, réinitialise le détecteur de boucles et oublie le dernier compte de tokens du serveur ; affiche `🆕 new session: N message(s) forgotten` (N compte tous les messages sauf le prompt système). | Traitée comme n'importe quel texte : annule la génération, conserve son historique partiel, puis s'exécute comme `/new` sur cet historique. |12+| `/new` | Ramène l'historique au seul prompt système, réinitialise le détecteur de boucles, oublie le dernier compte de tokens du serveur et les plans de la conversation ; affiche `🆕 new session: N message(s) forgotten` (N compte tous les messages sauf le prompt système). | Traitée comme n'importe quel texte : annule la génération, conserve son historique partiel, puis s'exécute comme `/new` sur cet historique. |
13 | `/sessions` | Liste les sessions enregistrées du projet, la plus récente d'abord : identifiant, dernière mise à jour, nombre de questions, première question, puis ` · ⚠️ N error(s)` quand la session a enregistré des erreurs ; `*` marque la session courante. Avec `sessions.enabled: false` : `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Envoyée au modèle comme nouveau message. |13 | `/sessions` | Liste les sessions enregistrées du projet, la plus récente d'abord : identifiant, dernière mise à jour, nombre de questions, première question, puis ` · ⚠️ N error(s)` quand la session a enregistré des erreurs ; `*` marque la session courante. Avec `sessions.enabled: false` : `⚠️ sessions are off (set sessions.enabled: true in the YAML)`. | Envoyée au modèle comme nouveau message. |
14-| `/resume [id]` | Reprend une session enregistrée : la plus récemment mise à jour autre que la courante, en ignorant les vides, ou l'identifiant donné. Affiche `↩️ resumed <id> — N message(s), started with "<première question>"` (suivi de ` · ⚠️ N error(s)` quand la session a enregistré des erreurs) ; réinitialise le détecteur de boucles et le compte de tokens du serveur ; les tours suivants sont enregistrés dans cette session. Refusée avec une raison `⚠️` quand les sessions sont désactivées, qu'il n'y a pas de session antérieure, que l'identifiant est inconnu ou désigne la session courante. | Envoyée au modèle comme nouveau message. |14+| `/resume [id]` | Reprend une session enregistrée : la plus récemment mise à jour autre que la courante, en ignorant les vides, ou l'identifiant donné. Affiche `↩️ resumed <id> — N message(s), started with "<première question>"` (suivi de ` · ⚠️ N error(s)` quand la session a enregistré des erreurs) ; réinitialise le détecteur de boucles, le compte de tokens du serveur et les plans ; les tours suivants sont enregistrés dans cette session. Refusée avec une raison `⚠️` quand les sessions sont désactivées, qu'il n'y a pas de session antérieure, que l'identifiant est inconnu ou désigne la session courante. | Envoyée au modèle comme nouveau message. |
15 | `/help` | Affiche les commandes de cette façade, une par ligne avec leur description, alignées. | Envoyée au modèle comme nouveau message. |15 | `/help` | Affiche les commandes de cette façade, une par ligne avec leur description, alignées. | Envoyée au modèle comme nouveau message. |
16 | `/save <fichier> [n]` | Écrit la dernière réponse (`n` : la n-ième depuis la fin) telle quelle dans un nouveau fichier, en créant les répertoires manquants, et affiche `💾 saved the last answer to <fichier> (N bytes)`. N'écrase jamais : un fichier existant donne `⚠️ <fichier> already exists — nothing was written; choose another name`. Un nom de fichier avec des espaces n'est pas pris en charge. Le modèle n'est pas appelé. | Envoyée au modèle comme nouveau message. |16 | `/save <fichier> [n]` | Écrit la dernière réponse (`n` : la n-ième depuis la fin) telle quelle dans un nouveau fichier, en créant les répertoires manquants, et affiche `💾 saved the last answer to <fichier> (N bytes)`. N'écrase jamais : un fichier existant donne `⚠️ <fichier> already exists — nothing was written; choose another name`. Un nom de fichier avec des espaces n'est pas pris en charge. Le modèle n'est pas appelé. | Envoyée au modèle comme nouveau message. |
17 | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory` : cherche dans les sessions enregistrées du projet et affiche `🔎 N hit(s) for "<mots>" in <rép>`, puis deux lignes par résultat — `<id de session> · <date> · turn <n> · question\|answer\|summary` et le texte autour des mots trouvés, plus `→ <réponse>` sous une question (la réponse sur laquelle son tour s'est terminé) — ou `🔎 no hit for "<mots>" in <rép>`. La casse et les accents ne comptent pas ; les mots d'une ou deux lettres sont ignorés à côté de mots plus longs ; un résultat contient au moins un des mots ; les résultats sont classés par nombre de mots contenus, puis du plus récent au plus ancien ; 10 au plus. Les questions, les réponses et les résumés de `/compact` sont cherchés, pas les appels d'outils ; la conversation en cours est exclue. Sans mots : `⚠️ nothing to search: give one or more words`. Le modèle n'est pas appelé ; l'historique n'est pas touché. | Envoyée au modèle comme nouveau message. |17 | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory` : cherche dans les sessions enregistrées du projet et affiche `🔎 N hit(s) for "<mots>" in <rép>`, puis deux lignes par résultat — `<id de session> · <date> · turn <n> · question\|answer\|summary` et le texte autour des mots trouvés, plus `→ <réponse>` sous une question (la réponse sur laquelle son tour s'est terminé) — ou `🔎 no hit for "<mots>" in <rép>`. La casse et les accents ne comptent pas ; les mots d'une ou deux lettres sont ignorés à côté de mots plus longs ; un résultat contient au moins un des mots ; les résultats sont classés par nombre de mots contenus, puis du plus récent au plus ancien ; 10 au plus. Les questions, les réponses et les résumés de `/compact` sont cherchés, pas les appels d'outils ; la conversation en cours est exclue. Sans mots : `⚠️ nothing to search: give one or more words`. Le modèle n'est pas appelé ; l'historique n'est pas touché. | Envoyée au modèle comme nouveau message. |
@@ -176,7 +176,7 @@ Deux commandes existent en mode ACP, trois avec `unloadModels` (le défaut) : `/
176 176
177 | Commande | En ACP |177 | Commande | En ACP |
178 |----------|--------|178 |----------|--------|
179-| `/new` | Comme dans le terminal : historique ramené au prompt système, compte de tokens oublié ; l'identifiant de session, le `cwd` et les autorisations « toujours autoriser » sont conservés. |179+| `/new` | Comme dans le terminal : historique ramené au prompt système, compte de tokens et plans de cette session oubliés (les autres sessions gardent les leurs) ; l'identifiant de session, le `cwd` et les autorisations « toujours autoriser » sont conservés. |
180 | `/compact` | Comme dans le terminal, toujours forcée : `🗜️ compressed …`, `🗜️ nothing to compact: …` ou `[compact: failed, history kept: …]`. Attend d'abord la fin d'un tour en cours. |180 | `/compact` | Comme dans le terminal, toujours forcée : `🗜️ compressed …`, `🗜️ nothing to compact: …` ou `[compact: failed, history kept: …]`. Attend d'abord la fin d'un tour en cours. |
181 | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, dans le projet de la session (`sessions.dir` sous son `cwd`), comme texte de l'agent ; l'historique n'est pas touché. |181 | `/memory <mots>` | Avec `sessions.enabled` et `sessions.searchMemory`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, dans le projet de la session (`sessions.dir` sous son `cwd`), comme texte de l'agent ; l'historique n'est pas touché. |
182 | `/docs <mots>` | Avec `docSearch.searchDocs`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, depuis le `cwd` de la session, comme texte de l'agent ; l'historique n'est pas touché. |182 | `/docs <mots>` | Avec `docSearch.searchDocs`, annoncée avec l'indication de saisie `<words>` : comme dans le terminal, depuis le `cwd` de la session, comme texte de l'agent ; l'historique n'est pas touché. |
modified docs/fr/reference/tools.md +32 -0
@@ -132,6 +132,38 @@ the closest the file comes to it — copy these lines:
132132 │ 6 │ }
133133 ```
134134
135+## `plan`
136+
137+Déclaré quand `planTool: true` (le défaut). Transforme une liste de tâches en un plan que le modèle met à jour au fil du travail. Voir [Gérer des plans et des tâches](../how-to/manage-plans-and-tasks.md).
138+
139+| Champ | Type | Description |
140+|-------|------|-------------|
141+| `action` | chaîne | Obligatoire. `create`, `get`, `update`, `next`, `list`, `delete` ou `status`. |
142+| `name` | chaîne | Le plan. Pour `create`, facultatif : un plan sans nom s'appelle `user-request-N` (N compte les plans de la conversation). Pour les autres actions, facultatif aussi : sans lui, l'action porte sur le dernier plan créé dans la conversation (`list` l'ignore). |
143+| `tasks` | liste de `{description, id?}` | Pour `create` : les tâches, dans l'ordre. Une tâche sans `id` reçoit sa position (1, 2, …). Ignoré par les autres actions. |
144+| `taskId` | entier | Pour `update` : la tâche, à partir de 1. |
145+| `status` | chaîne | Pour `update` : `open`, `in_progress`, `done` ou `failed`. |
146+| `message` | chaîne | Pour `update` : une note gardée sur la tâche. |
147+
148+| Action | Effet | Résultat (texte JSON) |
149+|--------|-------|-----------------------|
150+| `create` | Crée le plan, chaque tâche `open`. Refusé si le nom est déjà pris. | `result` (`Plan '<nom>' created with N tasks. Current: Task 1`), `planName`, `status`, `current`, `total` ; en mode ligne, aussi le `plan` entier. |
151+| `get` | Lit un plan. | `result`, `plan` (nom, tâches avec id, description, statut, heures de début et de fin, note). |
152+| `update` | Fixe le statut d'une tâche ; `in_progress` enregistre son heure de début (une fois) et en fait la tâche courante, `done` et `failed` enregistrent son heure de fin. | `result`, `status` (la ligne de statut, ci-dessous). |
153+| `next` | Marque la tâche courante `done` et démarre la suivante qui n'est ni faite ni en échec (`in_progress`) ; une tâche déjà terminée avec `update` est sautée. | `result` (`Task N done. Next: Task M - <description>`, ou `Plan '<nom>' is complete! All N tasks done.`), `status`, `nextTask` s'il y en a une. |
154+| `list` | Liste les plans existants. | `result`, `plans` (noms, du plus ancien au plus récent). |
155+| `delete` | Supprime un plan. | `result`. |
156+| `status` | Lit la ligne de statut. | `result` (`Plan '<nom>': D/N tasks complete`), `status`. |
157+
158+| Aspect | Comportement |
159+|--------|--------------|
160+| Ligne de statut | `[C/N] <icône> Task C: <description> (<statut>)` pour la tâche en cours de travail — la première en cours, sinon la première encore ouverte —, `[N/N] Plan finished: D done, F failed` quand toutes les tâches sont terminées et que certaines ont échoué, `[N/N] All tasks complete!` quand toutes sont faites. Icônes : ⏳ ouverte, 🔄 en cours, ✅ faite, ❌ en échec. |
161+| Erreurs | Renvoyées comme texte, jamais comme un échec de génération : `[error: no plans found for this session]`, `[error: no plan named '<nom>' found]`, `[error: plan '<nom>' already exists]`, `[error: no task with id N in plan '<nom>']`, `[error: unknown action '<action>'. Valid actions: …]`. Un `update` sans `status` valide renvoie l'appel reçu, précise que `tasks` est ignoré par `update` quand il a été envoyé, et donne un appel qui fonctionne. |
162+| Stockage | En mémoire, par conversation : le mode ligne et la fenêtre en tiennent une ; en ACP chaque session a la sienne, et un fil ne voit jamais les plans d'un autre. Les plans ne sont pas écrits dans le fichier de session et sont perdus à l'arrêt de mm. Une conversation qui change repart sans plan : `/new` et `/resume` dans toutes les façades, `session/load` et `session/resume` en ACP. |
163+| Permission | `create`, `update`, `next` et `delete` sont demandés quand `yolo: false` ; un refus renvoie `[plan: command rejected by the user]`. `get`, `list` et `status` ne demandent jamais. |
164+| Affichage | Mode ligne : `📋 plan: <action> <nom>` avant l'appel, puis `📋 <ligne de statut>` après chaque action qui en a une (`create`, `update`, `next`, `status`). Fenêtre : l'appel comme appel d'outil, et `📋 <ligne de statut>` en notice. Éditeur (ACP) : l'appel comme appel d'outil, le statut dans son résultat. La progression est affichée par mm, que le modèle la reprenne ou non. |
165+| ACP et fenêtre | Appel d'outil de type `edit` pour `create`, `update`, `next`, `delete`, `read` pour les autres ; un résultat commençant par `[error:` est un appel en échec. |
166+
135167 ## `lsp`
136168
137169 Déclaré quand `lsp.servers` liste au moins un serveur dont la commande est sur le `PATH` au démarrage. Un serveur listé mais introuvable est ignoré avec un `[warning: lsp server <nom> (<commande>) not found on the PATH — skipped]` ; quand aucun n'est trouvé, l'outil n'est pas déclaré et la bannière le dit.
@@ -132,6 +132,38 @@ the closest the file comes to it — copy these lines:
132 │ 6 │ }132 │ 6 │ }
133 ```133 ```
134 134
135+## `plan`
136+
137+Déclaré quand `planTool: true` (le défaut). Transforme une liste de tâches en un plan que le modèle met à jour au fil du travail. Voir [Gérer des plans et des tâches](../how-to/manage-plans-and-tasks.md).
138+
139+| Champ | Type | Description |
140+|-------|------|-------------|
141+| `action` | chaîne | Obligatoire. `create`, `get`, `update`, `next`, `list`, `delete` ou `status`. |
142+| `name` | chaîne | Le plan. Pour `create`, facultatif : un plan sans nom s'appelle `user-request-N` (N compte les plans de la conversation). Pour les autres actions, facultatif aussi : sans lui, l'action porte sur le dernier plan créé dans la conversation (`list` l'ignore). |
143+| `tasks` | liste de `{description, id?}` | Pour `create` : les tâches, dans l'ordre. Une tâche sans `id` reçoit sa position (1, 2, …). Ignoré par les autres actions. |
144+| `taskId` | entier | Pour `update` : la tâche, à partir de 1. |
145+| `status` | chaîne | Pour `update` : `open`, `in_progress`, `done` ou `failed`. |
146+| `message` | chaîne | Pour `update` : une note gardée sur la tâche. |
147+
148+| Action | Effet | Résultat (texte JSON) |
149+|--------|-------|-----------------------|
150+| `create` | Crée le plan, chaque tâche `open`. Refusé si le nom est déjà pris. | `result` (`Plan '<nom>' created with N tasks. Current: Task 1`), `planName`, `status`, `current`, `total` ; en mode ligne, aussi le `plan` entier. |
151+| `get` | Lit un plan. | `result`, `plan` (nom, tâches avec id, description, statut, heures de début et de fin, note). |
152+| `update` | Fixe le statut d'une tâche ; `in_progress` enregistre son heure de début (une fois) et en fait la tâche courante, `done` et `failed` enregistrent son heure de fin. | `result`, `status` (la ligne de statut, ci-dessous). |
153+| `next` | Marque la tâche courante `done` et démarre la suivante qui n'est ni faite ni en échec (`in_progress`) ; une tâche déjà terminée avec `update` est sautée. | `result` (`Task N done. Next: Task M - <description>`, ou `Plan '<nom>' is complete! All N tasks done.`), `status`, `nextTask` s'il y en a une. |
154+| `list` | Liste les plans existants. | `result`, `plans` (noms, du plus ancien au plus récent). |
155+| `delete` | Supprime un plan. | `result`. |
156+| `status` | Lit la ligne de statut. | `result` (`Plan '<nom>': D/N tasks complete`), `status`. |
157+
158+| Aspect | Comportement |
159+|--------|--------------|
160+| Ligne de statut | `[C/N] <icône> Task C: <description> (<statut>)` pour la tâche en cours de travail — la première en cours, sinon la première encore ouverte —, `[N/N] Plan finished: D done, F failed` quand toutes les tâches sont terminées et que certaines ont échoué, `[N/N] All tasks complete!` quand toutes sont faites. Icônes : ⏳ ouverte, 🔄 en cours, ✅ faite, ❌ en échec. |
161+| Erreurs | Renvoyées comme texte, jamais comme un échec de génération : `[error: no plans found for this session]`, `[error: no plan named '<nom>' found]`, `[error: plan '<nom>' already exists]`, `[error: no task with id N in plan '<nom>']`, `[error: unknown action '<action>'. Valid actions: …]`. Un `update` sans `status` valide renvoie l'appel reçu, précise que `tasks` est ignoré par `update` quand il a été envoyé, et donne un appel qui fonctionne. |
162+| Stockage | En mémoire, par conversation : le mode ligne et la fenêtre en tiennent une ; en ACP chaque session a la sienne, et un fil ne voit jamais les plans d'un autre. Les plans ne sont pas écrits dans le fichier de session et sont perdus à l'arrêt de mm. Une conversation qui change repart sans plan : `/new` et `/resume` dans toutes les façades, `session/load` et `session/resume` en ACP. |
163+| Permission | `create`, `update`, `next` et `delete` sont demandés quand `yolo: false` ; un refus renvoie `[plan: command rejected by the user]`. `get`, `list` et `status` ne demandent jamais. |
164+| Affichage | Mode ligne : `📋 plan: <action> <nom>` avant l'appel, puis `📋 <ligne de statut>` après chaque action qui en a une (`create`, `update`, `next`, `status`). Fenêtre : l'appel comme appel d'outil, et `📋 <ligne de statut>` en notice. Éditeur (ACP) : l'appel comme appel d'outil, le statut dans son résultat. La progression est affichée par mm, que le modèle la reprenne ou non. |
165+| ACP et fenêtre | Appel d'outil de type `edit` pour `create`, `update`, `next`, `delete`, `read` pour les autres ; un résultat commençant par `[error:` est un appel en échec. |
166+
135 ## `lsp`167 ## `lsp`
136 168
137 Déclaré quand `lsp.servers` liste au moins un serveur dont la commande est sur le `PATH` au démarrage. Un serveur listé mais introuvable est ignoré avec un `[warning: lsp server <nom> (<commande>) not found on the PATH — skipped]` ; quand aucun n'est trouvé, l'outil n'est pas déclaré et la bannière le dit.169 Déclaré quand `lsp.servers` liste au moins un serveur dont la commande est sur le `PATH` au démarrage. Un serveur listé mais introuvable est ignoré avec un `[warning: lsp server <nom> (<commande>) not found on the PATH — skipped]` ; quand aucun n'est trouvé, l'outil n'est pas déclaré et la bannière le dit.
modified internal/acp/acp.go +2 -0
@@ -41,6 +41,7 @@ import (
4141 history "mm/internal/session"
4242 "mm/internal/skills"
4343 "mm/internal/store"
44+ "mm/internal/tools"
4445 "mm/internal/ui"
4546 "mm/internal/version"
4647
@@ -573,6 +574,7 @@ func skillCommand(input string) string {
573574 func (f *front) startNewSession(ctx context.Context, sid sdk.SessionId, s *session) sdk.PromptResponse {
574575 f.archive(sid, s)
575576 forgotten := resetSession(s, f.system)
577+ tools.ClearPlans(string(sid)) // this thread's plans only (tickets 108, 109)
576578 f.e.ForgetInputTokens()
577579 s.usage.lastUsed = 0 // measured on the history just dropped; the totals stay, the tokens were spent
578580 f.persist(sid, s)
@@ -41,6 +41,7 @@ import (
41 history "mm/internal/session"41 history "mm/internal/session"
42 "mm/internal/skills"42 "mm/internal/skills"
43 "mm/internal/store"43 "mm/internal/store"
44+ "mm/internal/tools"
44 "mm/internal/ui"45 "mm/internal/ui"
45 "mm/internal/version"46 "mm/internal/version"
46 47
@@ -573,6 +574,7 @@ func skillCommand(input string) string {
573 func (f *front) startNewSession(ctx context.Context, sid sdk.SessionId, s *session) sdk.PromptResponse {574 func (f *front) startNewSession(ctx context.Context, sid sdk.SessionId, s *session) sdk.PromptResponse {
574 f.archive(sid, s)575 f.archive(sid, s)
575 forgotten := resetSession(s, f.system)576 forgotten := resetSession(s, f.system)
577+ tools.ClearPlans(string(sid)) // this thread's plans only (tickets 108, 109)
576 f.e.ForgetInputTokens()578 f.e.ForgetInputTokens()
577 s.usage.lastUsed = 0 // measured on the history just dropped; the totals stay, the tokens were spent579 s.usage.lastUsed = 0 // measured on the history just dropped; the totals stay, the tokens were spent
578 f.persist(sid, s)580 f.persist(sid, s)
modified internal/acp/persist.go +4 -0
@@ -10,6 +10,7 @@ import (
1010 "mm/internal/agentsmd"
1111 history "mm/internal/session"
1212 "mm/internal/store"
13+ "mm/internal/tools"
1314
1415 sdk "github.com/coder/acp-go-sdk"
1516 )
@@ -81,6 +82,9 @@ func (f *front) restore(method string, id sdk.SessionId, cwd string) (*session,
8182 f.mu.Lock()
8283 f.sessions[id] = s
8384 f.mu.Unlock()
85+ // Plans are not in the session file: a restored thread starts with none,
86+ // never with what this process held under the same id (ticket 109).
87+ tools.ClearPlans(string(id))
8488 return s, nil
8589 }
8690
@@ -10,6 +10,7 @@ import (
10 "mm/internal/agentsmd"10 "mm/internal/agentsmd"
11 history "mm/internal/session"11 history "mm/internal/session"
12 "mm/internal/store"12 "mm/internal/store"
13+ "mm/internal/tools"
13 14
14 sdk "github.com/coder/acp-go-sdk"15 sdk "github.com/coder/acp-go-sdk"
15 )16 )
@@ -81,6 +82,9 @@ func (f *front) restore(method string, id sdk.SessionId, cwd string) (*session,
81 f.mu.Lock()82 f.mu.Lock()
82 f.sessions[id] = s83 f.sessions[id] = s
83 f.mu.Unlock()84 f.mu.Unlock()
85+ // Plans are not in the session file: a restored thread starts with none,
86+ // never with what this process held under the same id (ticket 109).
87+ tools.ClearPlans(string(id))
84 return s, nil88 return s, nil
85 }89 }
86 90
added internal/acp/plans_test.go +102 -0
new file mode 100644
@@ -0,0 +1,102 @@
1+package acp
2+
3+import (
4+ "context"
5+ "path/filepath"
6+ "strings"
7+ "testing"
8+
9+ "mm/internal/detector"
10+ "mm/internal/engine"
11+ "mm/internal/store"
12+ "mm/internal/tools"
13+ "mm/internal/ui"
14+
15+ "github.com/firebase/genkit/go/genkit"
16+)
17+
18+// threadOf stands for a turn of one ACP session, as far as the plan tool can
19+// tell: a sink that names its conversation and draws nothing.
20+type threadOf struct{ id string }
21+
22+func (t threadOf) ConversationID() string { return t.id }
23+func (threadOf) Text(string) {}
24+func (threadOf) Reasoning(string) {}
25+func (threadOf) ToolStart(ui.ToolEvent) {}
26+func (threadOf) Allow(context.Context, ui.ToolEvent) bool { return true }
27+func (threadOf) ToolRunning(string) {}
28+func (threadOf) ToolEnd(string, ui.ToolResult) {}
29+func (threadOf) WorkDir() string { return "" }
30+
31+// /new in one editor thread clears that thread's plans and leaves the other
32+// threads' alone (tickets 108, 109).
33+func TestNewOverACPClearsOnlyItsThreadsPlans(t *testing.T) {
34+ tools.ClearAllPlansForTesting()
35+ t.Cleanup(tools.ClearAllPlansForTesting)
36+ g := genkit.Init(context.Background())
37+ tools.PlanTool(g, detector.NewLoopDetector(10, 3))
38+ plan := genkit.LookupTool(g, "plan")
39+ run := func(thread string, input map[string]any) string {
40+ ui.SetActive(threadOf{id: thread})
41+ defer ui.SetActive(nil)
42+ out, err := plan.RunRaw(context.Background(), input)
43+ if err != nil {
44+ t.Fatal(err)
45+ }
46+ return out.(string)
47+ }
48+
49+ w := newWire(t, "sys")
50+ frontOf(t, w).e = &engine.Engine{}
51+ open := func(id int) string {
52+ w.send(id, "session/new", `{"cwd":"`+t.TempDir()+`","mcpServers":[]}`)
53+ _, resp := w.until(id)
54+ return resp["result"].(map[string]any)["sessionId"].(string)
55+ }
56+ a, b := open(1), open(2)
57+ tasks := []any{map[string]any{"description": "x"}}
58+ run(a, map[string]any{"action": "create", "name": "pa", "tasks": tasks})
59+ run(b, map[string]any{"action": "create", "name": "pb", "tasks": tasks})
60+
61+ w.send(3, "session/prompt", `{"sessionId":"`+a+`","prompt":[{"type":"text","text":"/new"}]}`)
62+ w.until(3)
63+ if out := run(a, map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") {
64+ t.Errorf("thread A after its /new: %s", out)
65+ }
66+ if out := run(b, map[string]any{"action": "list"}); !strings.Contains(out, `"pb"`) {
67+ t.Errorf("thread B lost its plan to A's /new: %s", out)
68+ }
69+}
70+
71+// A thread restored by session/load starts with no plan, even when this
72+// process held plans under the same id: plans are not in the session file.
73+func TestLoadSessionStartsWithNoPlan(t *testing.T) {
74+ withSessions(t)
75+ tools.ClearAllPlansForTesting()
76+ t.Cleanup(tools.ClearAllPlansForTesting)
77+ g := genkit.Init(context.Background())
78+ tools.PlanTool(g, detector.NewLoopDetector(10, 3))
79+ plan := genkit.LookupTool(g, "plan")
80+ const id = "20260916-100000-aaaaaaaa"
81+ run := func(input map[string]any) string {
82+ ui.SetActive(threadOf{id: id})
83+ defer ui.SetActive(nil)
84+ out, err := plan.RunRaw(context.Background(), input)
85+ if err != nil {
86+ t.Fatal(err)
87+ }
88+ return out.(string)
89+ }
90+ cwd := t.TempDir()
91+ if err := store.Open(filepath.Join(cwd, ".mm", "sessions")).Save(&store.Session{ID: id, Cwd: cwd, Messages: storedHistory()}); err != nil {
92+ t.Fatal(err)
93+ }
94+ run(map[string]any{"action": "create", "name": "stale", "tasks": []any{map[string]any{"description": "x"}}})
95+
96+ w := newWire(t, "sys")
97+ w.send(1, "session/load", `{"sessionId":"`+id+`","cwd":"`+cwd+`","mcpServers":[]}`)
98+ w.until(1)
99+ if out := run(map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") {
100+ t.Errorf("after session/load: %s", out)
101+ }
102+}
new file mode 100644
@@ -0,0 +1,102 @@
1+package acp
2+
3+import (
4+ "context"
5+ "path/filepath"
6+ "strings"
7+ "testing"
8+
9+ "mm/internal/detector"
10+ "mm/internal/engine"
11+ "mm/internal/store"
12+ "mm/internal/tools"
13+ "mm/internal/ui"
14+
15+ "github.com/firebase/genkit/go/genkit"
16+)
17+
18+// threadOf stands for a turn of one ACP session, as far as the plan tool can
19+// tell: a sink that names its conversation and draws nothing.
20+type threadOf struct{ id string }
21+
22+func (t threadOf) ConversationID() string { return t.id }
23+func (threadOf) Text(string) {}
24+func (threadOf) Reasoning(string) {}
25+func (threadOf) ToolStart(ui.ToolEvent) {}
26+func (threadOf) Allow(context.Context, ui.ToolEvent) bool { return true }
27+func (threadOf) ToolRunning(string) {}
28+func (threadOf) ToolEnd(string, ui.ToolResult) {}
29+func (threadOf) WorkDir() string { return "" }
30+
31+// /new in one editor thread clears that thread's plans and leaves the other
32+// threads' alone (tickets 108, 109).
33+func TestNewOverACPClearsOnlyItsThreadsPlans(t *testing.T) {
34+ tools.ClearAllPlansForTesting()
35+ t.Cleanup(tools.ClearAllPlansForTesting)
36+ g := genkit.Init(context.Background())
37+ tools.PlanTool(g, detector.NewLoopDetector(10, 3))
38+ plan := genkit.LookupTool(g, "plan")
39+ run := func(thread string, input map[string]any) string {
40+ ui.SetActive(threadOf{id: thread})
41+ defer ui.SetActive(nil)
42+ out, err := plan.RunRaw(context.Background(), input)
43+ if err != nil {
44+ t.Fatal(err)
45+ }
46+ return out.(string)
47+ }
48+
49+ w := newWire(t, "sys")
50+ frontOf(t, w).e = &engine.Engine{}
51+ open := func(id int) string {
52+ w.send(id, "session/new", `{"cwd":"`+t.TempDir()+`","mcpServers":[]}`)
53+ _, resp := w.until(id)
54+ return resp["result"].(map[string]any)["sessionId"].(string)
55+ }
56+ a, b := open(1), open(2)
57+ tasks := []any{map[string]any{"description": "x"}}
58+ run(a, map[string]any{"action": "create", "name": "pa", "tasks": tasks})
59+ run(b, map[string]any{"action": "create", "name": "pb", "tasks": tasks})
60+
61+ w.send(3, "session/prompt", `{"sessionId":"`+a+`","prompt":[{"type":"text","text":"/new"}]}`)
62+ w.until(3)
63+ if out := run(a, map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") {
64+ t.Errorf("thread A after its /new: %s", out)
65+ }
66+ if out := run(b, map[string]any{"action": "list"}); !strings.Contains(out, `"pb"`) {
67+ t.Errorf("thread B lost its plan to A's /new: %s", out)
68+ }
69+}
70+
71+// A thread restored by session/load starts with no plan, even when this
72+// process held plans under the same id: plans are not in the session file.
73+func TestLoadSessionStartsWithNoPlan(t *testing.T) {
74+ withSessions(t)
75+ tools.ClearAllPlansForTesting()
76+ t.Cleanup(tools.ClearAllPlansForTesting)
77+ g := genkit.Init(context.Background())
78+ tools.PlanTool(g, detector.NewLoopDetector(10, 3))
79+ plan := genkit.LookupTool(g, "plan")
80+ const id = "20260916-100000-aaaaaaaa"
81+ run := func(input map[string]any) string {
82+ ui.SetActive(threadOf{id: id})
83+ defer ui.SetActive(nil)
84+ out, err := plan.RunRaw(context.Background(), input)
85+ if err != nil {
86+ t.Fatal(err)
87+ }
88+ return out.(string)
89+ }
90+ cwd := t.TempDir()
91+ if err := store.Open(filepath.Join(cwd, ".mm", "sessions")).Save(&store.Session{ID: id, Cwd: cwd, Messages: storedHistory()}); err != nil {
92+ t.Fatal(err)
93+ }
94+ run(map[string]any{"action": "create", "name": "stale", "tasks": []any{map[string]any{"description": "x"}}})
95+
96+ w := newWire(t, "sys")
97+ w.send(1, "session/load", `{"sessionId":"`+id+`","cwd":"`+cwd+`","mcpServers":[]}`)
98+ w.until(1)
99+ if out := run(map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") {
100+ t.Errorf("after session/load: %s", out)
101+ }
102+}
modified internal/acp/sink.go +4 -0
@@ -53,6 +53,10 @@ func (k *sink) Reasoning(text string) {
5353 // there, and relative file paths resolve against it (ui.Resolve).
5454 func (k *sink) WorkDir() string { return k.s.cwd }
5555
56+// ConversationID is the ACP session id: what keeps one thread's plans out of
57+// another's (ui.Conversation).
58+func (k *sink) ConversationID() string { return string(k.sid) }
59+
5660 // ToolStart announces the call, status pending, before anything runs — the
5761 // same promise as the terminal's 🛠️ line: it prints BEFORE execution, so its
5862 // absence proves nothing ran.
@@ -53,6 +53,10 @@ func (k *sink) Reasoning(text string) {
53 // there, and relative file paths resolve against it (ui.Resolve).53 // there, and relative file paths resolve against it (ui.Resolve).
54 func (k *sink) WorkDir() string { return k.s.cwd }54 func (k *sink) WorkDir() string { return k.s.cwd }
55 55
56+// ConversationID is the ACP session id: what keeps one thread's plans out of
57+// another's (ui.Conversation).
58+func (k *sink) ConversationID() string { return string(k.sid) }
59+
56 // ToolStart announces the call, status pending, before anything runs — the60 // ToolStart announces the call, status pending, before anything runs — the
57 // same promise as the terminal's 🛠️ line: it prints BEFORE execution, so its61 // same promise as the terminal's 🛠️ line: it prints BEFORE execution, so its
58 // absence proves nothing ran.62 // absence proves nothing ran.
modified internal/agent/agent.go +3 -2
@@ -484,8 +484,8 @@ func startNewSession(e *engine.Engine, d *detector.LoopDetector, system string,
484484 forgotten := session.Forgotten(old)
485485 e.ForgetInputTokens()
486486 d.Reset()
487- // Clear any active plans when starting a new session
488- tools.ClearAllPlans()
487+ // Plans belong to the conversation /new just closed (ticket 109).
488+ tools.ClearPlans("")
489489 dimln(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten))
490490 return session.Fresh(agentsmd.System(system, ""))
491491 }
@@ -551,6 +551,7 @@ func (l *loop) historyCommand(input string, messages []*ai.Message) ([]*ai.Messa
551551 messages = resumed
552552 l.d.Reset()
553553 l.e.ForgetInputTokens()
554+ tools.ClearPlans("") // the plans in memory were the previous conversation's
554555 fmt.Println(line)
555556 default:
556557 return messages, false
@@ -484,8 +484,8 @@ func startNewSession(e *engine.Engine, d *detector.LoopDetector, system string,
484 forgotten := session.Forgotten(old)484 forgotten := session.Forgotten(old)
485 e.ForgetInputTokens()485 e.ForgetInputTokens()
486 d.Reset()486 d.Reset()
487- // Clear any active plans when starting a new session487+ // Plans belong to the conversation /new just closed (ticket 109).
488- tools.ClearAllPlans()488+ tools.ClearPlans("")
489 dimln(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten))489 dimln(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten))
490 return session.Fresh(agentsmd.System(system, ""))490 return session.Fresh(agentsmd.System(system, ""))
491 }491 }
@@ -551,6 +551,7 @@ func (l *loop) historyCommand(input string, messages []*ai.Message) ([]*ai.Messa
551 messages = resumed551 messages = resumed
552 l.d.Reset()552 l.d.Reset()
553 l.e.ForgetInputTokens()553 l.e.ForgetInputTokens()
554+ tools.ClearPlans("") // the plans in memory were the previous conversation's
554 fmt.Println(line)555 fmt.Println(line)
555 default:556 default:
556 return messages, false557 return messages, false
modified internal/tools/plan.go +118 -37
@@ -38,6 +38,7 @@ type Plan struct {
3838 Tasks []PlanTask `json:"tasks"`
3939 Current int `json:"current"` // 0 = not started, 1 = first task, etc.
4040 CreatedAt string `json:"createdAt"` // ISO timestamp
41+ order int // creation order in the store: CreatedAt has one-second precision
4142 }
4243
4344 // PlanTask represents a single task within a plan.
@@ -55,7 +56,7 @@ type planInput struct {
5556 // Action is required: create, get, update, next, list, delete, status
5657 Action string `json:"action" jsonschema_description:"Required. One of: create, get, update, next, list, delete, status"`
5758 // Name is the plan identifier (default: 'default' for unnamed plans)
58- Name string `json:"name,omitempty" jsonschema_description:"Plan name. Defaults to 'user-request' if not provided."`
59+ Name string `json:"name,omitempty" jsonschema_description:"Plan name. For create, optional (a name is chosen). For the other actions, optional: without it, the most recently created plan is used."`
5960 // Tasks is used with 'create' action to define the plan's tasks
6061 Tasks []PlanTaskInput `json:"tasks,omitempty" jsonschema_description:"For create action: list of tasks to add"`
6162 // TaskID identifies which task for update/next actions (1-based)
@@ -91,6 +92,7 @@ type PlanStore struct {
9192 mu sync.RWMutex
9293 plans map[string]map[string]*Plan // sessionID -> planName -> Plan
9394 nextID map[string]int // sessionID -> next task ID
95+ made int // plans created so far, for Plan.order
9496 }
9597
9698 var planStore = &PlanStore{
@@ -98,14 +100,13 @@ var planStore = &PlanStore{
98100 nextID: make(map[string]int),
99101 }
100102
101-// getSessionID extracts a session identifier from the tool context.
102-// For simplicity in v1, we use a placeholder since the actual session ID
103-// isn't easily accessible from tool context. In practice, plans are cleared
104-// on /new command via ClearPlans().
103+// getSessionID is the conversation the plan call belongs to (ticket 108): the
104+// ACP session id, or "" in the line mode and the window, which hold one
105+// conversation at a time. It used to be a constant, so every thread an
106+// editor opened in one mm process saw — and could change — the others'
107+// plans.
105108 func getSessionID() string {
106- // v1: use a single session for simplicity
107- // v2: extract from context when available
108- return "current-session"
109+ return ui.ConversationID()
109110 }
110111
111112 // newPlanName generates a unique plan name if none is provided.
@@ -153,11 +154,13 @@ func (s *PlanStore) CreatePlan(name string, tasks []PlanTaskInput) (*Plan, strin
153154 }
154155 }
155156
157+ s.made++
156158 plan := &Plan{
157159 Name: name,
158160 Tasks: planTasks,
159161 Current: 0,
160162 CreatedAt: time.Now().UTC().Format(time.RFC3339),
163+ order: s.made,
161164 }
162165
163166 s.plans[sessionID][name] = plan
@@ -246,6 +249,9 @@ func (s *PlanStore) UpdateTask(name string, taskID int, status string, message s
246249 task := &plan.Tasks[taskIndex]
247250 task.Status = status
248251 task.Notes = message
252+ if status == "in_progress" {
253+ plan.Current = taskIndex + 1 // so that a later `next` moves on from here
254+ }
249255
250256 // Set timestamps
251257 now := time.Now().UTC().Format(time.RFC3339)
@@ -286,8 +292,12 @@ func (s *PlanStore) NextTask(name string) (*Plan, *PlanTask, string, error) {
286292 plan.Tasks[plan.Current-1].CompletedAt = time.Now().UTC().Format(time.RFC3339)
287293 }
288294
289- // Move to next
295+ // Move to the next task that still needs work: a task the model already
296+ // marked done with `update` is skipped, never started again.
290297 plan.Current++
298+ for plan.Current <= len(plan.Tasks) && finished(plan.Tasks[plan.Current-1]) {
299+ plan.Current++
300+ }
291301
292302 // Return next task
293303 if plan.Current <= len(plan.Tasks) {
@@ -345,28 +355,42 @@ func (s *PlanStore) GetStatus(name string) (string, string, error) {
345355 }
346356 }
347357
348- // Format status line
349- // Current=0 means not started yet, Task 1 is next
350- // Current>0 means we've started, Task Current is the next one
351- if plan.Current == 0 {
352- // Not started, show Task 1
353- task := plan.Tasks[0]
354- return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", 1, len(plan.Tasks), statusIcon(task.Status), 1, task.Description, task.Status),
355- fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks)), nil
358+ // The line shows the task being worked on — the first in progress, else
359+ // the first still open — whatever the model used to get there: `update`
360+ // does not move plan.Current, so following Current alone showed task 1
361+ // for a plan whose task 1 was done and task 2 in progress (ticket 110).
362+ summary := fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks))
363+ if i := workingTask(plan); i >= 0 {
364+ task := plan.Tasks[i]
365+ return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", i+1, len(plan.Tasks), statusIcon(task.Status), i+1, task.Description, task.Status), summary, nil
356366 }
357-
358- // Current > 0, show Task Current
359- if plan.Current <= len(plan.Tasks) {
360- task := plan.Tasks[plan.Current-1]
361- return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", plan.Current, len(plan.Tasks), statusIcon(task.Status), plan.Current, task.Description, task.Status),
362- fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks)), nil
367+ if doneCount < len(plan.Tasks) {
368+ failed := len(plan.Tasks) - doneCount
369+ return fmt.Sprintf("[%d/%d] Plan finished: %d done, %d failed", len(plan.Tasks), len(plan.Tasks), doneCount, failed), summary, nil
363370 }
364-
365- // All tasks complete
366371 return fmt.Sprintf("[%d/%d] All tasks complete!", len(plan.Tasks), len(plan.Tasks)),
367372 fmt.Sprintf("Plan '%s': All %d tasks done ✅", name, len(plan.Tasks)), nil
368373 }
369374
375+// workingTask is the index of the task a plan is at: the first in progress,
376+// else the first still open; -1 when every task is done or failed.
377+func workingTask(plan *Plan) int {
378+ for i, t := range plan.Tasks {
379+ if t.Status == "in_progress" {
380+ return i
381+ }
382+ }
383+ for i, t := range plan.Tasks {
384+ if t.Status == "open" {
385+ return i
386+ }
387+ }
388+ return -1
389+}
390+
391+// finished reports whether a task needs no more work.
392+func finished(t PlanTask) bool { return t.Status == "done" || t.Status == "failed" }
393+
370394 // statusIcon is the display mark for a task status.
371395 func statusIcon(status string) string {
372396 switch status {
@@ -381,13 +405,29 @@ func statusIcon(status string) string {
381405 }
382406 }
383407
384-// ClearPlans removes all plans for the current session.
385-// Called when /new command is issued.
386-func (s *PlanStore) ClearPlans() {
387- sessionID := getSessionID()
408+// LatestName is the name of the conversation's most recently created plan,
409+// "" when it has none. It is what an action without a name means: a small
410+// model does not keep the name `create` returned — measured on Qwen3.5 4B
411+// (demos/plans, 2026-10-21), every `update` came without one, failed with
412+// "no plan named ”", and the turn ended in runaway tool calls.
413+func (s *PlanStore) LatestName() string {
414+ conversation := getSessionID()
415+ s.mu.RLock()
416+ defer s.mu.RUnlock()
417+ latest, order := "", -1
418+ for name, p := range s.plans[conversation] {
419+ if p.order > order {
420+ latest, order = name, p.order
421+ }
422+ }
423+ return latest
424+}
425+
426+// ClearPlans removes the plans of one conversation.
427+func (s *PlanStore) ClearPlans(conversation string) {
388428 s.mu.Lock()
389429 defer s.mu.Unlock()
390- delete(s.plans, sessionID)
430+ delete(s.plans, conversation)
391431 }
392432
393433 // ClearAllPlans removes all plans. Used for testing.
@@ -434,16 +474,23 @@ Always show the user the current plan status using the status action.`
434474 // Terminal/REPL: announce the call
435475 spinner.Suspend(func() {
436476 printMu.Lock()
437- fmt.Printf("\n📋 plan: %s %s\n", in.Action, in.Name)
477+ fmt.Printf("\n📋 %s\n", planTitle(in))
438478 printMu.Unlock()
439479 })
440- if planChanges(in.Action) && !confirmed(tctx, "plan", fmt.Sprintf("plan: %s %s", in.Action, in.Name)) {
480+ if planChanges(in.Action) && !confirmed(tctx, "plan", planTitle(in)) {
441481 inputJSON, _ := json.Marshal(in)
442482 return refuse(d, "plan", string(inputJSON), "[plan: command rejected by the user]")
443483 }
444484
445485 spinner.Start("Running")
446486 result := runPlanAction(in, true)
487+ if line := progressLine(result); line != "" {
488+ spinner.Suspend(func() {
489+ printMu.Lock()
490+ fmt.Println(line)
491+ printMu.Unlock()
492+ })
493+ }
447494
448495 // Record in loop detector
449496 inputJSON, _ := json.Marshal(in)
@@ -451,6 +498,26 @@ Always show the user the current plan status using the status action.`
451498 })
452499 }
453500
501+// planTitle names a call: `plan: update user-request-1`, `plan: list`.
502+func planTitle(in planInput) string {
503+ return strings.TrimSpace("plan: " + in.Action + " " + in.Name)
504+}
505+
506+// progressLine is what mm shows the user after a plan action (ticket 110):
507+// the plan's status line — `📋 [2/3] 🔄 Task 2: Add a license
508+// (in_progress)` — taken from the result, "" when the action has none (get,
509+// list, delete, an error). Shown by mm itself, so the progress does not
510+// depend on the model repeating it.
511+func progressLine(result string) string {
512+ var out struct {
513+ Status string `json:"status"`
514+ }
515+ if json.Unmarshal([]byte(result), &out) != nil || out.Status == "" {
516+ return ""
517+ }
518+ return "📋 " + out.Status
519+}
520+
454521 // planChanges reports whether a plan action changes a plan — the ones that
455522 // ask for permission when yolo is off.
456523 func planChanges(action string) bool {
@@ -467,7 +534,7 @@ func planEvented(ctx context.Context, s ui.Sink, d *detector.LoopDetector, in pl
467534 ev := ui.ToolEvent{
468535 ID: ui.NextCallID(),
469536 Tool: "plan",
470- Title: fmt.Sprintf("plan: %s %s", in.Action, in.Name),
537+ Title: planTitle(in),
471538 Kind: eventKind,
472539 Input: map[string]any{"action": in.Action, "name": in.Name, "taskId": in.TaskID, "status": in.Status},
473540 }
@@ -492,16 +559,30 @@ func planEvented(ctx context.Context, s ui.Sink, d *detector.LoopDetector, in pl
492559 failed := strings.HasPrefix(result, "[error:")
493560
494561 s.ToolEnd(ev.ID, ui.ToolResult{Output: result, Failed: failed})
562+ // The window shows the progress as a notice; an editor has none, and
563+ // shows the status inside the tool call's result.
564+ if n, ok := s.(ui.Noticer); ok {
565+ if line := progressLine(result); line != "" {
566+ n.Notice(line)
567+ }
568+ }
495569
496570 // Record in loop detector
497571 inputJSON, _ := json.Marshal(in)
498572 return record(d, "plan", string(inputJSON), result)
499573 }
500574
501-// ClearAllPlans clears all plans for the current session.
502-// Called when a new session starts (/new command).
503-func ClearAllPlans() {
504- planStore.ClearPlans()
575+// ClearPlans forgets the plans of a conversation: "" for the line mode and
576+// the window, the session id over ACP (ui.ConversationID). Plans are not
577+// saved in the session file, so every front end calls it when the
578+// conversation changes — /new, /resume, and over ACP session/load and
579+// session/resume (ticket 109): a new or restored conversation starts with
580+// no plan, never with the previous one's.
581+//
582+// tools.ClearPlans("") // the terminal's /new
583+// tools.ClearPlans(string(sid)) // an ACP session's /new
584+func ClearPlans(conversation string) {
585+ planStore.ClearPlans(conversation)
505586 }
506587
507588 // ClearAllPlansForTesting exposes ClearAllPlans for tests.
@@ -38,6 +38,7 @@ type Plan struct {
38 Tasks []PlanTask `json:"tasks"`38 Tasks []PlanTask `json:"tasks"`
39 Current int `json:"current"` // 0 = not started, 1 = first task, etc.39 Current int `json:"current"` // 0 = not started, 1 = first task, etc.
40 CreatedAt string `json:"createdAt"` // ISO timestamp40 CreatedAt string `json:"createdAt"` // ISO timestamp
41+ order int // creation order in the store: CreatedAt has one-second precision
41 }42 }
42 43
43 // PlanTask represents a single task within a plan.44 // PlanTask represents a single task within a plan.
@@ -55,7 +56,7 @@ type planInput struct {
55 // Action is required: create, get, update, next, list, delete, status56 // Action is required: create, get, update, next, list, delete, status
56 Action string `json:"action" jsonschema_description:"Required. One of: create, get, update, next, list, delete, status"`57 Action string `json:"action" jsonschema_description:"Required. One of: create, get, update, next, list, delete, status"`
57 // Name is the plan identifier (default: 'default' for unnamed plans)58 // Name is the plan identifier (default: 'default' for unnamed plans)
58- Name string `json:"name,omitempty" jsonschema_description:"Plan name. Defaults to 'user-request' if not provided."`59+ Name string `json:"name,omitempty" jsonschema_description:"Plan name. For create, optional (a name is chosen). For the other actions, optional: without it, the most recently created plan is used."`
59 // Tasks is used with 'create' action to define the plan's tasks60 // Tasks is used with 'create' action to define the plan's tasks
60 Tasks []PlanTaskInput `json:"tasks,omitempty" jsonschema_description:"For create action: list of tasks to add"`61 Tasks []PlanTaskInput `json:"tasks,omitempty" jsonschema_description:"For create action: list of tasks to add"`
61 // TaskID identifies which task for update/next actions (1-based)62 // TaskID identifies which task for update/next actions (1-based)
@@ -91,6 +92,7 @@ type PlanStore struct {
91 mu sync.RWMutex92 mu sync.RWMutex
92 plans map[string]map[string]*Plan // sessionID -> planName -> Plan93 plans map[string]map[string]*Plan // sessionID -> planName -> Plan
93 nextID map[string]int // sessionID -> next task ID94 nextID map[string]int // sessionID -> next task ID
95+ made int // plans created so far, for Plan.order
94 }96 }
95 97
96 var planStore = &PlanStore{98 var planStore = &PlanStore{
@@ -98,14 +100,13 @@ var planStore = &PlanStore{
98 nextID: make(map[string]int),100 nextID: make(map[string]int),
99 }101 }
100 102
101-// getSessionID extracts a session identifier from the tool context.103+// getSessionID is the conversation the plan call belongs to (ticket 108): the
102-// For simplicity in v1, we use a placeholder since the actual session ID104+// ACP session id, or "" in the line mode and the window, which hold one
103-// isn't easily accessible from tool context. In practice, plans are cleared105+// conversation at a time. It used to be a constant, so every thread an
104-// on /new command via ClearPlans().106+// editor opened in one mm process saw — and could change — the others'
107+// plans.
105 func getSessionID() string {108 func getSessionID() string {
106- // v1: use a single session for simplicity109+ return ui.ConversationID()
107- // v2: extract from context when available
108- return "current-session"
109 }110 }
110 111
111 // newPlanName generates a unique plan name if none is provided.112 // newPlanName generates a unique plan name if none is provided.
@@ -153,11 +154,13 @@ func (s *PlanStore) CreatePlan(name string, tasks []PlanTaskInput) (*Plan, strin
153 }154 }
154 }155 }
155 156
157+ s.made++
156 plan := &Plan{158 plan := &Plan{
157 Name: name,159 Name: name,
158 Tasks: planTasks,160 Tasks: planTasks,
159 Current: 0,161 Current: 0,
160 CreatedAt: time.Now().UTC().Format(time.RFC3339),162 CreatedAt: time.Now().UTC().Format(time.RFC3339),
163+ order: s.made,
161 }164 }
162 165
163 s.plans[sessionID][name] = plan166 s.plans[sessionID][name] = plan
@@ -246,6 +249,9 @@ func (s *PlanStore) UpdateTask(name string, taskID int, status string, message s
246 task := &plan.Tasks[taskIndex]249 task := &plan.Tasks[taskIndex]
247 task.Status = status250 task.Status = status
248 task.Notes = message251 task.Notes = message
252+ if status == "in_progress" {
253+ plan.Current = taskIndex + 1 // so that a later `next` moves on from here
254+ }
249 255
250 // Set timestamps256 // Set timestamps
251 now := time.Now().UTC().Format(time.RFC3339)257 now := time.Now().UTC().Format(time.RFC3339)
@@ -286,8 +292,12 @@ func (s *PlanStore) NextTask(name string) (*Plan, *PlanTask, string, error) {
286 plan.Tasks[plan.Current-1].CompletedAt = time.Now().UTC().Format(time.RFC3339)292 plan.Tasks[plan.Current-1].CompletedAt = time.Now().UTC().Format(time.RFC3339)
287 }293 }
288 294
289- // Move to next295+ // Move to the next task that still needs work: a task the model already
296+ // marked done with `update` is skipped, never started again.
290 plan.Current++297 plan.Current++
298+ for plan.Current <= len(plan.Tasks) && finished(plan.Tasks[plan.Current-1]) {
299+ plan.Current++
300+ }
291 301
292 // Return next task302 // Return next task
293 if plan.Current <= len(plan.Tasks) {303 if plan.Current <= len(plan.Tasks) {
@@ -345,28 +355,42 @@ func (s *PlanStore) GetStatus(name string) (string, string, error) {
345 }355 }
346 }356 }
347 357
348- // Format status line358+ // The line shows the task being worked on — the first in progress, else
349- // Current=0 means not started yet, Task 1 is next359+ // the first still open — whatever the model used to get there: `update`
350- // Current>0 means we've started, Task Current is the next one360+ // does not move plan.Current, so following Current alone showed task 1
351- if plan.Current == 0 {361+ // for a plan whose task 1 was done and task 2 in progress (ticket 110).
352- // Not started, show Task 1362+ summary := fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks))
353- task := plan.Tasks[0]363+ if i := workingTask(plan); i >= 0 {
354- return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", 1, len(plan.Tasks), statusIcon(task.Status), 1, task.Description, task.Status),364+ task := plan.Tasks[i]
355- fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks)), nil365+ return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", i+1, len(plan.Tasks), statusIcon(task.Status), i+1, task.Description, task.Status), summary, nil
356 }366 }
357-367+ if doneCount < len(plan.Tasks) {
358- // Current > 0, show Task Current368+ failed := len(plan.Tasks) - doneCount
359- if plan.Current <= len(plan.Tasks) {369+ return fmt.Sprintf("[%d/%d] Plan finished: %d done, %d failed", len(plan.Tasks), len(plan.Tasks), doneCount, failed), summary, nil
360- task := plan.Tasks[plan.Current-1]
361- return fmt.Sprintf("[%d/%d] %s Task %d: %s (%s)", plan.Current, len(plan.Tasks), statusIcon(task.Status), plan.Current, task.Description, task.Status),
362- fmt.Sprintf("Plan '%s': %d/%d tasks complete", name, doneCount, len(plan.Tasks)), nil
363 }370 }
364-
365- // All tasks complete
366 return fmt.Sprintf("[%d/%d] All tasks complete!", len(plan.Tasks), len(plan.Tasks)),371 return fmt.Sprintf("[%d/%d] All tasks complete!", len(plan.Tasks), len(plan.Tasks)),
367 fmt.Sprintf("Plan '%s': All %d tasks done ✅", name, len(plan.Tasks)), nil372 fmt.Sprintf("Plan '%s': All %d tasks done ✅", name, len(plan.Tasks)), nil
368 }373 }
369 374
375+// workingTask is the index of the task a plan is at: the first in progress,
376+// else the first still open; -1 when every task is done or failed.
377+func workingTask(plan *Plan) int {
378+ for i, t := range plan.Tasks {
379+ if t.Status == "in_progress" {
380+ return i
381+ }
382+ }
383+ for i, t := range plan.Tasks {
384+ if t.Status == "open" {
385+ return i
386+ }
387+ }
388+ return -1
389+}
390+
391+// finished reports whether a task needs no more work.
392+func finished(t PlanTask) bool { return t.Status == "done" || t.Status == "failed" }
393+
370 // statusIcon is the display mark for a task status.394 // statusIcon is the display mark for a task status.
371 func statusIcon(status string) string {395 func statusIcon(status string) string {
372 switch status {396 switch status {
@@ -381,13 +405,29 @@ func statusIcon(status string) string {
381 }405 }
382 }406 }
383 407
384-// ClearPlans removes all plans for the current session.408+// LatestName is the name of the conversation's most recently created plan,
385-// Called when /new command is issued.409+// "" when it has none. It is what an action without a name means: a small
386-func (s *PlanStore) ClearPlans() {410+// model does not keep the name `create` returned — measured on Qwen3.5 4B
387- sessionID := getSessionID()411+// (demos/plans, 2026-10-21), every `update` came without one, failed with
412+// "no plan named ”", and the turn ended in runaway tool calls.
413+func (s *PlanStore) LatestName() string {
414+ conversation := getSessionID()
415+ s.mu.RLock()
416+ defer s.mu.RUnlock()
417+ latest, order := "", -1
418+ for name, p := range s.plans[conversation] {
419+ if p.order > order {
420+ latest, order = name, p.order
421+ }
422+ }
423+ return latest
424+}
425+
426+// ClearPlans removes the plans of one conversation.
427+func (s *PlanStore) ClearPlans(conversation string) {
388 s.mu.Lock()428 s.mu.Lock()
389 defer s.mu.Unlock()429 defer s.mu.Unlock()
390- delete(s.plans, sessionID)430+ delete(s.plans, conversation)
391 }431 }
392 432
393 // ClearAllPlans removes all plans. Used for testing.433 // ClearAllPlans removes all plans. Used for testing.
@@ -434,16 +474,23 @@ Always show the user the current plan status using the status action.`
434 // Terminal/REPL: announce the call474 // Terminal/REPL: announce the call
435 spinner.Suspend(func() {475 spinner.Suspend(func() {
436 printMu.Lock()476 printMu.Lock()
437- fmt.Printf("\n📋 plan: %s %s\n", in.Action, in.Name)477+ fmt.Printf("\n📋 %s\n", planTitle(in))
438 printMu.Unlock()478 printMu.Unlock()
439 })479 })
440- if planChanges(in.Action) && !confirmed(tctx, "plan", fmt.Sprintf("plan: %s %s", in.Action, in.Name)) {480+ if planChanges(in.Action) && !confirmed(tctx, "plan", planTitle(in)) {
441 inputJSON, _ := json.Marshal(in)481 inputJSON, _ := json.Marshal(in)
442 return refuse(d, "plan", string(inputJSON), "[plan: command rejected by the user]")482 return refuse(d, "plan", string(inputJSON), "[plan: command rejected by the user]")
443 }483 }
444 484
445 spinner.Start("Running")485 spinner.Start("Running")
446 result := runPlanAction(in, true)486 result := runPlanAction(in, true)
487+ if line := progressLine(result); line != "" {
488+ spinner.Suspend(func() {
489+ printMu.Lock()
490+ fmt.Println(line)
491+ printMu.Unlock()
492+ })
493+ }
447 494
448 // Record in loop detector495 // Record in loop detector
449 inputJSON, _ := json.Marshal(in)496 inputJSON, _ := json.Marshal(in)
@@ -451,6 +498,26 @@ Always show the user the current plan status using the status action.`
451 })498 })
452 }499 }
453 500
501+// planTitle names a call: `plan: update user-request-1`, `plan: list`.
502+func planTitle(in planInput) string {
503+ return strings.TrimSpace("plan: " + in.Action + " " + in.Name)
504+}
505+
506+// progressLine is what mm shows the user after a plan action (ticket 110):
507+// the plan's status line — `📋 [2/3] 🔄 Task 2: Add a license
508+// (in_progress)` — taken from the result, "" when the action has none (get,
509+// list, delete, an error). Shown by mm itself, so the progress does not
510+// depend on the model repeating it.
511+func progressLine(result string) string {
512+ var out struct {
513+ Status string `json:"status"`
514+ }
515+ if json.Unmarshal([]byte(result), &out) != nil || out.Status == "" {
516+ return ""
517+ }
518+ return "📋 " + out.Status
519+}
520+
454 // planChanges reports whether a plan action changes a plan — the ones that521 // planChanges reports whether a plan action changes a plan — the ones that
455 // ask for permission when yolo is off.522 // ask for permission when yolo is off.
456 func planChanges(action string) bool {523 func planChanges(action string) bool {
@@ -467,7 +534,7 @@ func planEvented(ctx context.Context, s ui.Sink, d *detector.LoopDetector, in pl
467 ev := ui.ToolEvent{534 ev := ui.ToolEvent{
468 ID: ui.NextCallID(),535 ID: ui.NextCallID(),
469 Tool: "plan",536 Tool: "plan",
470- Title: fmt.Sprintf("plan: %s %s", in.Action, in.Name),537+ Title: planTitle(in),
471 Kind: eventKind,538 Kind: eventKind,
472 Input: map[string]any{"action": in.Action, "name": in.Name, "taskId": in.TaskID, "status": in.Status},539 Input: map[string]any{"action": in.Action, "name": in.Name, "taskId": in.TaskID, "status": in.Status},
473 }540 }
@@ -492,16 +559,30 @@ func planEvented(ctx context.Context, s ui.Sink, d *detector.LoopDetector, in pl
492 failed := strings.HasPrefix(result, "[error:")559 failed := strings.HasPrefix(result, "[error:")
493 560
494 s.ToolEnd(ev.ID, ui.ToolResult{Output: result, Failed: failed})561 s.ToolEnd(ev.ID, ui.ToolResult{Output: result, Failed: failed})
562+ // The window shows the progress as a notice; an editor has none, and
563+ // shows the status inside the tool call's result.
564+ if n, ok := s.(ui.Noticer); ok {
565+ if line := progressLine(result); line != "" {
566+ n.Notice(line)
567+ }
568+ }
495 569
496 // Record in loop detector570 // Record in loop detector
497 inputJSON, _ := json.Marshal(in)571 inputJSON, _ := json.Marshal(in)
498 return record(d, "plan", string(inputJSON), result)572 return record(d, "plan", string(inputJSON), result)
499 }573 }
500 574
501-// ClearAllPlans clears all plans for the current session.575+// ClearPlans forgets the plans of a conversation: "" for the line mode and
502-// Called when a new session starts (/new command).576+// the window, the session id over ACP (ui.ConversationID). Plans are not
503-func ClearAllPlans() {577+// saved in the session file, so every front end calls it when the
504- planStore.ClearPlans()578+// conversation changes — /new, /resume, and over ACP session/load and
579+// session/resume (ticket 109): a new or restored conversation starts with
580+// no plan, never with the previous one's.
581+//
582+// tools.ClearPlans("") // the terminal's /new
583+// tools.ClearPlans(string(sid)) // an ACP session's /new
584+func ClearPlans(conversation string) {
585+ planStore.ClearPlans(conversation)
505 }586 }
506 587
507 // ClearAllPlansForTesting exposes ClearAllPlans for tests.588 // ClearAllPlansForTesting exposes ClearAllPlans for tests.
modified internal/tools/plan_actions.go +5 -1
@@ -25,6 +25,9 @@ func runPlanAction(in planInput, withPlan bool) string {
2525 if in.Action == "create" {
2626 return planCreate(in, withPlan)
2727 }
28+ if in.Name == "" && in.Action != "list" {
29+ in.Name = planStore.LatestName()
30+ }
2831 if run, ok := planActions[in.Action]; ok {
2932 return run(in)
3033 }
@@ -47,7 +50,8 @@ func planCreate(in planInput, withPlan bool) string {
4750 if err != nil {
4851 return planError(err)
4952 }
50- out := PlanOutput{Result: msg, PlanName: plan.Name, Current: plan.Current, Total: len(plan.Tasks)}
53+ statusLine, _, _ := planStore.GetStatus(plan.Name)
54+ out := PlanOutput{Result: msg, PlanName: plan.Name, Status: statusLine, Current: plan.Current, Total: len(plan.Tasks)}
5155 if withPlan {
5256 out.Plan = plan
5357 }
@@ -25,6 +25,9 @@ func runPlanAction(in planInput, withPlan bool) string {
25 if in.Action == "create" {25 if in.Action == "create" {
26 return planCreate(in, withPlan)26 return planCreate(in, withPlan)
27 }27 }
28+ if in.Name == "" && in.Action != "list" {
29+ in.Name = planStore.LatestName()
30+ }
28 if run, ok := planActions[in.Action]; ok {31 if run, ok := planActions[in.Action]; ok {
29 return run(in)32 return run(in)
30 }33 }
@@ -47,7 +50,8 @@ func planCreate(in planInput, withPlan bool) string {
47 if err != nil {50 if err != nil {
48 return planError(err)51 return planError(err)
49 }52 }
50- out := PlanOutput{Result: msg, PlanName: plan.Name, Current: plan.Current, Total: len(plan.Tasks)}53+ statusLine, _, _ := planStore.GetStatus(plan.Name)
54+ out := PlanOutput{Result: msg, PlanName: plan.Name, Status: statusLine, Current: plan.Current, Total: len(plan.Tasks)}
51 if withPlan {55 if withPlan {
52 out.Plan = plan56 out.Plan = plan
53 }57 }
modified internal/tools/plan_actions_test.go +58 -2
@@ -2,6 +2,7 @@ package tools
22
33 import (
44 "encoding/json"
5+ "strconv"
56 "strings"
67 "testing"
78 )
@@ -29,8 +30,10 @@ func TestRunPlanAction(t *testing.T) {
2930 }{
3031 {planInput{Action: "get", Name: "p1"}, `"plan":`},
3132 {planInput{Action: "update", Name: "p1", TaskID: 1, Status: "in_progress"}, `"status":"[1/2]`},
32- {planInput{Action: "next", Name: "p1"}, `"nextTask":{"id":1,"description":"say \"hi\""}`},
33- {planInput{Action: "status", Name: "p1"}, `"status":"[1/2]`},
33+ // next ends the task in progress (1) and starts the following one —
34+ // it used to start task 1 again (ticket 110).
35+ {planInput{Action: "next", Name: "p1"}, `"nextTask":{"id":2,"description":"B"}`},
36+ {planInput{Action: "status", Name: "p1"}, `"status":"[2/2] 🔄 Task 2: B (in_progress)"`},
3437 {planInput{Action: "list"}, `"plans":["`},
3538 {planInput{Action: "delete", Name: "p2"}, `{"result":"`},
3639 {planInput{Action: "get", Name: "p2"}, "[error: "},
@@ -83,3 +86,56 @@ func TestPlanUpdateWithAnUnknownStatus(t *testing.T) {
8386 t.Errorf("answer %s lacks the default example", got)
8487 }
8588 }
89+
90+// A description with quotes stays valid JSON in next's nextTask.
91+func TestPlanNextEscapesTheDescription(t *testing.T) {
92+ ClearAllPlansForTesting()
93+ t.Cleanup(ClearAllPlansForTesting)
94+ runPlanAction(planInput{Action: "create", Name: "q", Tasks: []PlanTaskInput{{Description: `say "hi"`}}}, true)
95+ if got := runPlanAction(planInput{Action: "next", Name: "q"}, true); !strings.Contains(got, `"nextTask":{"id":1,"description":"say \"hi\""}`) {
96+ t.Errorf("next: %s", got)
97+ }
98+}
99+
100+// The status line follows the tasks, however the model moved through them:
101+// marking task 1 done with update, then next, starts task 2 (it used to put
102+// the finished task 1 back in progress); a plan whose tasks are all over
103+// with failures says so.
104+func TestPlanStatusFollowsTheTasks(t *testing.T) {
105+ ClearAllPlansForTesting()
106+ t.Cleanup(ClearAllPlansForTesting)
107+ runPlanAction(planInput{Action: "create", Name: "s", Tasks: []PlanTaskInput{{Description: "one"}, {Description: "two"}}}, true)
108+ for _, c := range []struct {
109+ in planInput
110+ want string
111+ }{
112+ {planInput{Action: "update", Name: "s", TaskID: 1, Status: "done"}, "[2/2] ⏳ Task 2: two (open)"},
113+ {planInput{Action: "next", Name: "s"}, "[2/2] 🔄 Task 2: two (in_progress)"},
114+ {planInput{Action: "update", Name: "s", TaskID: 2, Status: "failed"}, "[2/2] Plan finished: 1 done, 1 failed"},
115+ {planInput{Action: "update", Name: "s", TaskID: 2, Status: "done"}, "[2/2] All tasks complete!"},
116+ } {
117+ if got := runPlanAction(c.in, true); !strings.Contains(got, `"status":"`+c.want+`"`) {
118+ t.Errorf("%s %d %s: %s, want status %q", c.in.Action, c.in.TaskID, c.in.Status, got, c.want)
119+ }
120+ }
121+}
122+
123+// An action without a name works on the conversation's latest plan — a
124+// small model does not keep the name create returned (demos/plans).
125+func TestPlanActionWithoutANameUsesTheLatestPlan(t *testing.T) {
126+ ClearAllPlansForTesting()
127+ t.Cleanup(ClearAllPlansForTesting)
128+ if got := runPlanAction(planInput{Action: "update", TaskID: 1, Status: "done"}, true); !strings.Contains(got, "[error: no plans found") {
129+ t.Errorf("no plan yet: %s", got)
130+ }
131+ for i := range 11 { // past user-request-9, where names stop sorting by number
132+ runPlanAction(planInput{Action: "create", Tasks: []PlanTaskInput{{Description: "t" + strconv.Itoa(i)}}}, true)
133+ }
134+ got := runPlanAction(planInput{Action: "update", TaskID: 1, Status: "in_progress"}, true)
135+ if !strings.Contains(got, "in plan 'user-request-11'") || !strings.Contains(got, "Task 1: t10 (in_progress)") {
136+ t.Errorf("update without a name: %s", got)
137+ }
138+ if got := runPlanAction(planInput{Action: "status"}, true); !strings.Contains(got, "user-request-11") {
139+ t.Errorf("status without a name: %s", got)
140+ }
141+}
@@ -2,6 +2,7 @@ package tools
2 2
3 import (3 import (
4 "encoding/json"4 "encoding/json"
5+ "strconv"
5 "strings"6 "strings"
6 "testing"7 "testing"
7 )8 )
@@ -29,8 +30,10 @@ func TestRunPlanAction(t *testing.T) {
29 }{30 }{
30 {planInput{Action: "get", Name: "p1"}, `"plan":`},31 {planInput{Action: "get", Name: "p1"}, `"plan":`},
31 {planInput{Action: "update", Name: "p1", TaskID: 1, Status: "in_progress"}, `"status":"[1/2]`},32 {planInput{Action: "update", Name: "p1", TaskID: 1, Status: "in_progress"}, `"status":"[1/2]`},
32- {planInput{Action: "next", Name: "p1"}, `"nextTask":{"id":1,"description":"say \"hi\""}`},33+ // next ends the task in progress (1) and starts the following one —
33- {planInput{Action: "status", Name: "p1"}, `"status":"[1/2]`},34+ // it used to start task 1 again (ticket 110).
35+ {planInput{Action: "next", Name: "p1"}, `"nextTask":{"id":2,"description":"B"}`},
36+ {planInput{Action: "status", Name: "p1"}, `"status":"[2/2] 🔄 Task 2: B (in_progress)"`},
34 {planInput{Action: "list"}, `"plans":["`},37 {planInput{Action: "list"}, `"plans":["`},
35 {planInput{Action: "delete", Name: "p2"}, `{"result":"`},38 {planInput{Action: "delete", Name: "p2"}, `{"result":"`},
36 {planInput{Action: "get", Name: "p2"}, "[error: "},39 {planInput{Action: "get", Name: "p2"}, "[error: "},
@@ -83,3 +86,56 @@ func TestPlanUpdateWithAnUnknownStatus(t *testing.T) {
83 t.Errorf("answer %s lacks the default example", got)86 t.Errorf("answer %s lacks the default example", got)
84 }87 }
85 }88 }
89+
90+// A description with quotes stays valid JSON in next's nextTask.
91+func TestPlanNextEscapesTheDescription(t *testing.T) {
92+ ClearAllPlansForTesting()
93+ t.Cleanup(ClearAllPlansForTesting)
94+ runPlanAction(planInput{Action: "create", Name: "q", Tasks: []PlanTaskInput{{Description: `say "hi"`}}}, true)
95+ if got := runPlanAction(planInput{Action: "next", Name: "q"}, true); !strings.Contains(got, `"nextTask":{"id":1,"description":"say \"hi\""}`) {
96+ t.Errorf("next: %s", got)
97+ }
98+}
99+
100+// The status line follows the tasks, however the model moved through them:
101+// marking task 1 done with update, then next, starts task 2 (it used to put
102+// the finished task 1 back in progress); a plan whose tasks are all over
103+// with failures says so.
104+func TestPlanStatusFollowsTheTasks(t *testing.T) {
105+ ClearAllPlansForTesting()
106+ t.Cleanup(ClearAllPlansForTesting)
107+ runPlanAction(planInput{Action: "create", Name: "s", Tasks: []PlanTaskInput{{Description: "one"}, {Description: "two"}}}, true)
108+ for _, c := range []struct {
109+ in planInput
110+ want string
111+ }{
112+ {planInput{Action: "update", Name: "s", TaskID: 1, Status: "done"}, "[2/2] ⏳ Task 2: two (open)"},
113+ {planInput{Action: "next", Name: "s"}, "[2/2] 🔄 Task 2: two (in_progress)"},
114+ {planInput{Action: "update", Name: "s", TaskID: 2, Status: "failed"}, "[2/2] Plan finished: 1 done, 1 failed"},
115+ {planInput{Action: "update", Name: "s", TaskID: 2, Status: "done"}, "[2/2] All tasks complete!"},
116+ } {
117+ if got := runPlanAction(c.in, true); !strings.Contains(got, `"status":"`+c.want+`"`) {
118+ t.Errorf("%s %d %s: %s, want status %q", c.in.Action, c.in.TaskID, c.in.Status, got, c.want)
119+ }
120+ }
121+}
122+
123+// An action without a name works on the conversation's latest plan — a
124+// small model does not keep the name create returned (demos/plans).
125+func TestPlanActionWithoutANameUsesTheLatestPlan(t *testing.T) {
126+ ClearAllPlansForTesting()
127+ t.Cleanup(ClearAllPlansForTesting)
128+ if got := runPlanAction(planInput{Action: "update", TaskID: 1, Status: "done"}, true); !strings.Contains(got, "[error: no plans found") {
129+ t.Errorf("no plan yet: %s", got)
130+ }
131+ for i := range 11 { // past user-request-9, where names stop sorting by number
132+ runPlanAction(planInput{Action: "create", Tasks: []PlanTaskInput{{Description: "t" + strconv.Itoa(i)}}}, true)
133+ }
134+ got := runPlanAction(planInput{Action: "update", TaskID: 1, Status: "in_progress"}, true)
135+ if !strings.Contains(got, "in plan 'user-request-11'") || !strings.Contains(got, "Task 1: t10 (in_progress)") {
136+ t.Errorf("update without a name: %s", got)
137+ }
138+ if got := runPlanAction(planInput{Action: "status"}, true); !strings.Contains(got, "user-request-11") {
139+ t.Errorf("status without a name: %s", got)
140+ }
141+}
added internal/tools/plan_conversation_test.go +131 -0
new file mode 100644
@@ -0,0 +1,131 @@
1+package tools
2+
3+import (
4+ "context"
5+ "io"
6+ "os"
7+ "strings"
8+ "testing"
9+
10+ "mm/internal/detector"
11+ "mm/internal/ui"
12+
13+ "github.com/firebase/genkit/go/genkit"
14+)
15+
16+// threadSink is an ACP-like sink: one conversation among several, with no
17+// notices (an editor shows the status inside the tool call's result).
18+type threadSink struct {
19+ recordingSink
20+ id string
21+}
22+
23+func (k *threadSink) ConversationID() string { return k.id }
24+
25+// windowSink is a window-like sink: one conversation, and notices.
26+type windowSink struct {
27+ recordingSink
28+ notices []string
29+}
30+
31+func (k *windowSink) Notice(text string) { k.notices = append(k.notices, text) }
32+
33+func runPlan(t *testing.T, g *genkit.Genkit, sink ui.Sink, input map[string]any) string {
34+ t.Helper()
35+ ui.SetActive(sink)
36+ defer ui.SetActive(nil)
37+ out, err := genkit.LookupTool(g, "plan").RunRaw(context.Background(), input)
38+ if err != nil {
39+ t.Fatal(err)
40+ }
41+ return out.(string)
42+}
43+
44+// Ticket 108: two editor threads served by one process keep their own plans
45+// — names, lists and reads never cross — and clearing one thread's plans
46+// leaves the other's.
47+func TestPlansArePerConversation(t *testing.T) {
48+ ClearAllPlansForTesting()
49+ t.Cleanup(ClearAllPlansForTesting)
50+ g := genkit.Init(context.Background())
51+ PlanTool(g, detector.NewLoopDetector(10, 3))
52+ a, b := &threadSink{id: "thread-a"}, &threadSink{id: "thread-b"}
53+ tasks := []any{map[string]any{"description": "one"}, map[string]any{"description": "two"}}
54+
55+ if out := runPlan(t, g, a, map[string]any{"action": "create", "tasks": tasks}); !strings.Contains(out, `"planName":"user-request-1"`) {
56+ t.Fatalf("thread A create: %s", out)
57+ }
58+ if out := runPlan(t, g, b, map[string]any{"action": "create", "tasks": tasks}); !strings.Contains(out, `"planName":"user-request-1"`) {
59+ t.Errorf("thread B's first plan is not user-request-1 — the names count A's plans: %s", out)
60+ }
61+ runPlan(t, g, b, map[string]any{"action": "create", "name": "only-in-b", "tasks": tasks})
62+ if out := runPlan(t, g, a, map[string]any{"action": "list"}); strings.Contains(out, "only-in-b") || !strings.Contains(out, "Found 1 plan(s)") {
63+ t.Errorf("thread A lists B's plans: %s", out)
64+ }
65+ if out := runPlan(t, g, a, map[string]any{"action": "get", "name": "only-in-b"}); !strings.Contains(out, "no plan named 'only-in-b' found") {
66+ t.Errorf("thread A reads B's plan: %s", out)
67+ }
68+
69+ ClearPlans("thread-a")
70+ if out := runPlan(t, g, a, map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") {
71+ t.Errorf("thread A after its /new: %s", out)
72+ }
73+ if out := runPlan(t, g, b, map[string]any{"action": "list"}); !strings.Contains(out, "Found 2 plan(s)") {
74+ t.Errorf("clearing A touched B: %s", out)
75+ }
76+}
77+
78+// Ticket 110: mm shows the plan's progress itself — the status line, after
79+// every action that has one, create included — as a notice in the window.
80+// An editor's sink has no notices: nothing extra is sent there.
81+func TestPlanProgressIsShownByMm(t *testing.T) {
82+ ClearAllPlansForTesting()
83+ t.Cleanup(ClearAllPlansForTesting)
84+ g := genkit.Init(context.Background())
85+ PlanTool(g, detector.NewLoopDetector(10, 3))
86+ w := &windowSink{}
87+ tasks := []any{map[string]any{"description": "Create a README"}, map[string]any{"description": "Add a license"}}
88+
89+ runPlan(t, g, w, map[string]any{"action": "create", "name": "p", "tasks": tasks})
90+ runPlan(t, g, w, map[string]any{"action": "next", "name": "p"})
91+ runPlan(t, g, w, map[string]any{"action": "next", "name": "p"})
92+ runPlan(t, g, w, map[string]any{"action": "list"})
93+ want := []string{
94+ "📋 [1/2] ⏳ Task 1: Create a README (open)",
95+ "📋 [1/2] 🔄 Task 1: Create a README (in_progress)", // next starts task 1
96+ "📋 [2/2] 🔄 Task 2: Add a license (in_progress)", // next: 1 done, 2 started
97+ }
98+ if strings.Join(w.notices, "\n") != strings.Join(want, "\n") {
99+ t.Errorf("notices:\n%s\nwant:\n%s", strings.Join(w.notices, "\n"), strings.Join(want, "\n"))
100+ }
101+ if got := progressLine(`[error: no plan named 'x' found]`); got != "" {
102+ t.Errorf("an error gave a progress line: %q", got)
103+ }
104+}
105+
106+// In the line mode (no sink) the progress line is printed after the call
107+// line, on stdout.
108+func TestPlanProgressLineInTheLineMode(t *testing.T) {
109+ ClearAllPlansForTesting()
110+ t.Cleanup(ClearAllPlansForTesting)
111+ yoloOn(t)
112+ g := genkit.Init(context.Background())
113+ PlanTool(g, detector.NewLoopDetector(10, 3))
114+
115+ r, w, err := os.Pipe()
116+ if err != nil {
117+ t.Fatal(err)
118+ }
119+ stdout := os.Stdout
120+ os.Stdout = w
121+ _, err = genkit.LookupTool(g, "plan").RunRaw(context.Background(), map[string]any{"action": "create", "name": "p", "tasks": []any{map[string]any{"description": "Write the README"}}})
122+ os.Stdout = stdout
123+ _ = w.Close()
124+ if err != nil {
125+ t.Fatal(err)
126+ }
127+ printed, _ := io.ReadAll(r)
128+ if got := string(printed); !strings.Contains(got, "📋 plan: create p\n") || !strings.Contains(got, "📋 [1/1] ⏳ Task 1: Write the README (open)\n") {
129+ t.Errorf("printed:\n%s", got)
130+ }
131+}
new file mode 100644
@@ -0,0 +1,131 @@
1+package tools
2+
3+import (
4+ "context"
5+ "io"
6+ "os"
7+ "strings"
8+ "testing"
9+
10+ "mm/internal/detector"
11+ "mm/internal/ui"
12+
13+ "github.com/firebase/genkit/go/genkit"
14+)
15+
16+// threadSink is an ACP-like sink: one conversation among several, with no
17+// notices (an editor shows the status inside the tool call's result).
18+type threadSink struct {
19+ recordingSink
20+ id string
21+}
22+
23+func (k *threadSink) ConversationID() string { return k.id }
24+
25+// windowSink is a window-like sink: one conversation, and notices.
26+type windowSink struct {
27+ recordingSink
28+ notices []string
29+}
30+
31+func (k *windowSink) Notice(text string) { k.notices = append(k.notices, text) }
32+
33+func runPlan(t *testing.T, g *genkit.Genkit, sink ui.Sink, input map[string]any) string {
34+ t.Helper()
35+ ui.SetActive(sink)
36+ defer ui.SetActive(nil)
37+ out, err := genkit.LookupTool(g, "plan").RunRaw(context.Background(), input)
38+ if err != nil {
39+ t.Fatal(err)
40+ }
41+ return out.(string)
42+}
43+
44+// Ticket 108: two editor threads served by one process keep their own plans
45+// — names, lists and reads never cross — and clearing one thread's plans
46+// leaves the other's.
47+func TestPlansArePerConversation(t *testing.T) {
48+ ClearAllPlansForTesting()
49+ t.Cleanup(ClearAllPlansForTesting)
50+ g := genkit.Init(context.Background())
51+ PlanTool(g, detector.NewLoopDetector(10, 3))
52+ a, b := &threadSink{id: "thread-a"}, &threadSink{id: "thread-b"}
53+ tasks := []any{map[string]any{"description": "one"}, map[string]any{"description": "two"}}
54+
55+ if out := runPlan(t, g, a, map[string]any{"action": "create", "tasks": tasks}); !strings.Contains(out, `"planName":"user-request-1"`) {
56+ t.Fatalf("thread A create: %s", out)
57+ }
58+ if out := runPlan(t, g, b, map[string]any{"action": "create", "tasks": tasks}); !strings.Contains(out, `"planName":"user-request-1"`) {
59+ t.Errorf("thread B's first plan is not user-request-1 — the names count A's plans: %s", out)
60+ }
61+ runPlan(t, g, b, map[string]any{"action": "create", "name": "only-in-b", "tasks": tasks})
62+ if out := runPlan(t, g, a, map[string]any{"action": "list"}); strings.Contains(out, "only-in-b") || !strings.Contains(out, "Found 1 plan(s)") {
63+ t.Errorf("thread A lists B's plans: %s", out)
64+ }
65+ if out := runPlan(t, g, a, map[string]any{"action": "get", "name": "only-in-b"}); !strings.Contains(out, "no plan named 'only-in-b' found") {
66+ t.Errorf("thread A reads B's plan: %s", out)
67+ }
68+
69+ ClearPlans("thread-a")
70+ if out := runPlan(t, g, a, map[string]any{"action": "list"}); !strings.Contains(out, "No active plans") {
71+ t.Errorf("thread A after its /new: %s", out)
72+ }
73+ if out := runPlan(t, g, b, map[string]any{"action": "list"}); !strings.Contains(out, "Found 2 plan(s)") {
74+ t.Errorf("clearing A touched B: %s", out)
75+ }
76+}
77+
78+// Ticket 110: mm shows the plan's progress itself — the status line, after
79+// every action that has one, create included — as a notice in the window.
80+// An editor's sink has no notices: nothing extra is sent there.
81+func TestPlanProgressIsShownByMm(t *testing.T) {
82+ ClearAllPlansForTesting()
83+ t.Cleanup(ClearAllPlansForTesting)
84+ g := genkit.Init(context.Background())
85+ PlanTool(g, detector.NewLoopDetector(10, 3))
86+ w := &windowSink{}
87+ tasks := []any{map[string]any{"description": "Create a README"}, map[string]any{"description": "Add a license"}}
88+
89+ runPlan(t, g, w, map[string]any{"action": "create", "name": "p", "tasks": tasks})
90+ runPlan(t, g, w, map[string]any{"action": "next", "name": "p"})
91+ runPlan(t, g, w, map[string]any{"action": "next", "name": "p"})
92+ runPlan(t, g, w, map[string]any{"action": "list"})
93+ want := []string{
94+ "📋 [1/2] ⏳ Task 1: Create a README (open)",
95+ "📋 [1/2] 🔄 Task 1: Create a README (in_progress)", // next starts task 1
96+ "📋 [2/2] 🔄 Task 2: Add a license (in_progress)", // next: 1 done, 2 started
97+ }
98+ if strings.Join(w.notices, "\n") != strings.Join(want, "\n") {
99+ t.Errorf("notices:\n%s\nwant:\n%s", strings.Join(w.notices, "\n"), strings.Join(want, "\n"))
100+ }
101+ if got := progressLine(`[error: no plan named 'x' found]`); got != "" {
102+ t.Errorf("an error gave a progress line: %q", got)
103+ }
104+}
105+
106+// In the line mode (no sink) the progress line is printed after the call
107+// line, on stdout.
108+func TestPlanProgressLineInTheLineMode(t *testing.T) {
109+ ClearAllPlansForTesting()
110+ t.Cleanup(ClearAllPlansForTesting)
111+ yoloOn(t)
112+ g := genkit.Init(context.Background())
113+ PlanTool(g, detector.NewLoopDetector(10, 3))
114+
115+ r, w, err := os.Pipe()
116+ if err != nil {
117+ t.Fatal(err)
118+ }
119+ stdout := os.Stdout
120+ os.Stdout = w
121+ _, err = genkit.LookupTool(g, "plan").RunRaw(context.Background(), map[string]any{"action": "create", "name": "p", "tasks": []any{map[string]any{"description": "Write the README"}}})
122+ os.Stdout = stdout
123+ _ = w.Close()
124+ if err != nil {
125+ t.Fatal(err)
126+ }
127+ printed, _ := io.ReadAll(r)
128+ if got := string(printed); !strings.Contains(got, "📋 plan: create p\n") || !strings.Contains(got, "📋 [1/1] ⏳ Task 1: Write the README (open)\n") {
129+ t.Errorf("printed:\n%s", got)
130+ }
131+}
modified internal/tools/plan_test.go +1 -1
@@ -429,7 +429,7 @@ func TestPlanStore_ClearPlans(t *testing.T) {
429429 }
430430
431431 // Clear plans
432- store.ClearPlans()
432+ store.ClearPlans("")
433433
434434 // Verify plans cleared
435435 plans, _, _ = store.ListPlans()
@@ -429,7 +429,7 @@ func TestPlanStore_ClearPlans(t *testing.T) {
429 }429 }
430 430
431 // Clear plans431 // Clear plans
432- store.ClearPlans()432+ store.ClearPlans("")
433 433
434 // Verify plans cleared434 // Verify plans cleared
435 plans, _, _ = store.ListPlans()435 plans, _, _ = store.ListPlans()
modified internal/tui/commands.go +3 -0
@@ -13,6 +13,7 @@ import (
1313 "mm/internal/docsearch"
1414 "mm/internal/session"
1515 "mm/internal/store"
16+ "mm/internal/tools"
1617 )
1718
1819 // submit is what Enter does with the box: a slash command or a question.
@@ -159,6 +160,7 @@ func (r *runner) newSession() {
159160 r.opts.Model.ForgetInputTokens()
160161 r.opts.Detector.Reset()
161162 r.keep.Renew()
163+ tools.ClearPlans("") // plans belong to the conversation just closed (ticket 109)
162164 r.t.Notice(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten))
163165 }
164166
@@ -171,6 +173,7 @@ func (r *runner) resume(id string) {
171173 return
172174 }
173175 r.messages = msgs
176+ tools.ClearPlans("") // the plans in memory were the previous conversation's
174177 r.opts.Detector.Reset()
175178 r.opts.Model.ForgetInputTokens()
176179 r.t.Notice(line)
@@ -13,6 +13,7 @@ import (
13 "mm/internal/docsearch"13 "mm/internal/docsearch"
14 "mm/internal/session"14 "mm/internal/session"
15 "mm/internal/store"15 "mm/internal/store"
16+ "mm/internal/tools"
16 )17 )
17 18
18 // submit is what Enter does with the box: a slash command or a question.19 // submit is what Enter does with the box: a slash command or a question.
@@ -159,6 +160,7 @@ func (r *runner) newSession() {
159 r.opts.Model.ForgetInputTokens()160 r.opts.Model.ForgetInputTokens()
160 r.opts.Detector.Reset()161 r.opts.Detector.Reset()
161 r.keep.Renew()162 r.keep.Renew()
163+ tools.ClearPlans("") // plans belong to the conversation just closed (ticket 109)
162 r.t.Notice(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten))164 r.t.Notice(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten))
163 }165 }
164 166
@@ -171,6 +173,7 @@ func (r *runner) resume(id string) {
171 return173 return
172 }174 }
173 r.messages = msgs175 r.messages = msgs
176+ tools.ClearPlans("") // the plans in memory were the previous conversation's
174 r.opts.Detector.Reset()177 r.opts.Detector.Reset()
175 r.opts.Model.ForgetInputTokens()178 r.opts.Model.ForgetInputTokens()
176 r.t.Notice(line)179 r.t.Notice(line)
modified internal/tui/turn_test.go +42 -0
@@ -16,9 +16,11 @@ import (
1616 mmhooks "mm/internal/hooks"
1717 "mm/internal/session"
1818 "mm/internal/store"
19+ "mm/internal/tools"
1920 "mm/internal/ui"
2021
2122 "github.com/firebase/genkit/go/ai"
23+ "github.com/firebase/genkit/go/genkit"
2224 )
2325
2426 // fakeModel answers with a canned text; its first call can block until
@@ -654,3 +656,43 @@ func TestRunnerDocs(t *testing.T) {
654656 t.Errorf("notices:\n%s", n)
655657 }
656658 }
659+
660+// /new and /resume start the next conversation with no plan: the plans in
661+// memory belonged to the one just closed (ticket 109).
662+func TestRunnerNewAndResumeClearThePlans(t *testing.T) {
663+ tools.ClearAllPlansForTesting()
664+ t.Cleanup(tools.ClearAllPlansForTesting)
665+ g := genkit.Init(context.Background())
666+ tools.PlanTool(g, detector.NewLoopDetector(10, 3))
667+ plan := genkit.LookupTool(g, "plan")
668+ list := func() string {
669+ out, err := plan.RunRaw(context.Background(), map[string]any{"action": "list"})
670+ if err != nil {
671+ t.Fatal(err)
672+ }
673+ return out.(string)
674+ }
675+ create := func() {
676+ if _, err := plan.RunRaw(context.Background(), map[string]any{"action": "create", "name": "p", "tasks": []any{map[string]any{"description": "x"}}}); err != nil {
677+ t.Fatal(err)
678+ }
679+ }
680+
681+ r, _ := newTestRunner(t, &fakeModel{})
682+ create()
683+ r.newSession()
684+ if out := list(); !strings.Contains(out, "No active plans") {
685+ t.Errorf("after /new: %s", out)
686+ }
687+
688+ config.Cfg.Sessions = config.SessionsConfig{Enabled: true, Dir: ".mm/sessions"}
689+ r.keep = store.NewKeeper(r.opts.Cwd)
690+ if err := store.Open(r.keep.Dir()).Save(&store.Session{ID: "20260101-000000-aaaaaaaa", Messages: []*ai.Message{ai.NewUserTextMessage("old question")}}); err != nil {
691+ t.Fatal(err)
692+ }
693+ create()
694+ r.resume("20260101-000000-aaaaaaaa")
695+ if out := list(); !strings.Contains(out, "No active plans") {
696+ t.Errorf("after /resume: %s", out)
697+ }
698+}
@@ -16,9 +16,11 @@ import (
16 mmhooks "mm/internal/hooks"16 mmhooks "mm/internal/hooks"
17 "mm/internal/session"17 "mm/internal/session"
18 "mm/internal/store"18 "mm/internal/store"
19+ "mm/internal/tools"
19 "mm/internal/ui"20 "mm/internal/ui"
20 21
21 "github.com/firebase/genkit/go/ai"22 "github.com/firebase/genkit/go/ai"
23+ "github.com/firebase/genkit/go/genkit"
22 )24 )
23 25
24 // fakeModel answers with a canned text; its first call can block until26 // fakeModel answers with a canned text; its first call can block until
@@ -654,3 +656,43 @@ func TestRunnerDocs(t *testing.T) {
654 t.Errorf("notices:\n%s", n)656 t.Errorf("notices:\n%s", n)
655 }657 }
656 }658 }
659+
660+// /new and /resume start the next conversation with no plan: the plans in
661+// memory belonged to the one just closed (ticket 109).
662+func TestRunnerNewAndResumeClearThePlans(t *testing.T) {
663+ tools.ClearAllPlansForTesting()
664+ t.Cleanup(tools.ClearAllPlansForTesting)
665+ g := genkit.Init(context.Background())
666+ tools.PlanTool(g, detector.NewLoopDetector(10, 3))
667+ plan := genkit.LookupTool(g, "plan")
668+ list := func() string {
669+ out, err := plan.RunRaw(context.Background(), map[string]any{"action": "list"})
670+ if err != nil {
671+ t.Fatal(err)
672+ }
673+ return out.(string)
674+ }
675+ create := func() {
676+ if _, err := plan.RunRaw(context.Background(), map[string]any{"action": "create", "name": "p", "tasks": []any{map[string]any{"description": "x"}}}); err != nil {
677+ t.Fatal(err)
678+ }
679+ }
680+
681+ r, _ := newTestRunner(t, &fakeModel{})
682+ create()
683+ r.newSession()
684+ if out := list(); !strings.Contains(out, "No active plans") {
685+ t.Errorf("after /new: %s", out)
686+ }
687+
688+ config.Cfg.Sessions = config.SessionsConfig{Enabled: true, Dir: ".mm/sessions"}
689+ r.keep = store.NewKeeper(r.opts.Cwd)
690+ if err := store.Open(r.keep.Dir()).Save(&store.Session{ID: "20260101-000000-aaaaaaaa", Messages: []*ai.Message{ai.NewUserTextMessage("old question")}}); err != nil {
691+ t.Fatal(err)
692+ }
693+ create()
694+ r.resume("20260101-000000-aaaaaaaa")
695+ if out := list(); !strings.Contains(out, "No active plans") {
696+ t.Errorf("after /resume: %s", out)
697+ }
698+}
modified internal/ui/ui.go +20 -0
@@ -114,6 +114,26 @@ type Elicitor interface {
114114 Elicit(ctx context.Context, ev ToolEvent, f elicit.Form) elicit.Result
115115 }
116116
117+// Conversation is the optional side of a Sink that serves one conversation
118+// among several in the same process — ACP, where one mm serves every thread
119+// the editor opens. A tool that keeps state per conversation (the plan tool)
120+// reads it through ConversationID.
121+type Conversation interface {
122+ ConversationID() string
123+}
124+
125+// ConversationID names the conversation the turn in progress belongs to: the
126+// active sink's Conversation, or "" for the front ends that hold one
127+// conversation at a time (the line mode, the window).
128+//
129+// key := ui.ConversationID() // "20261021-…" over ACP, "" in a terminal
130+func ConversationID() string {
131+ if c, ok := ActiveSink().(Conversation); ok {
132+ return c.ConversationID()
133+ }
134+ return ""
135+}
136+
117137 // Noticer is the optional side of a Sink that shows a one-line notice — a
118138 // remark about the turn that is neither the answer nor a tool call, such as
119139 // a hook that failed. The window has one (a dim transcript line); without
@@ -114,6 +114,26 @@ type Elicitor interface {
114 Elicit(ctx context.Context, ev ToolEvent, f elicit.Form) elicit.Result114 Elicit(ctx context.Context, ev ToolEvent, f elicit.Form) elicit.Result
115 }115 }
116 116
117+// Conversation is the optional side of a Sink that serves one conversation
118+// among several in the same process — ACP, where one mm serves every thread
119+// the editor opens. A tool that keeps state per conversation (the plan tool)
120+// reads it through ConversationID.
121+type Conversation interface {
122+ ConversationID() string
123+}
124+
125+// ConversationID names the conversation the turn in progress belongs to: the
126+// active sink's Conversation, or "" for the front ends that hold one
127+// conversation at a time (the line mode, the window).
128+//
129+// key := ui.ConversationID() // "20261021-…" over ACP, "" in a terminal
130+func ConversationID() string {
131+ if c, ok := ActiveSink().(Conversation); ok {
132+ return c.ConversationID()
133+ }
134+ return ""
135+}
136+
117 // Noticer is the optional side of a Sink that shows a one-line notice — a137 // Noticer is the optional side of a Sink that shows a one-line notice — a
118 // remark about the turn that is neither the answer nor a tool call, such as138 // remark about the turn that is neither the answer nor a tool call, such as
119 // a hook that failed. The window has one (a dim transcript line); without139 // a hook that failed. The window has one (a dim transcript line); without
modified internal/version/about.go +4 -2
@@ -26,8 +26,10 @@ func About() string {
2626 b.WriteString(" mm [config.yaml] a terminal REPL, line by line\n")
2727 b.WriteString(" mm -tui [config.yaml] one terminal window: conversation, input box, status bar\n")
2828 b.WriteString(" mm -acp the Agent Client Protocol on stdio, for Zed and other editors\n\n")
29- b.WriteString("Tools the model can call: bash, read_skill, read_file, write_file, edit_file, lsp.\n")
30- b.WriteString("Model servers: Docker Model Runner, llama.cpp — anything speaking the OpenAI API.\n\n")
29+ b.WriteString("Built-in tools: bash, read_file, write_file, edit_file, read_skill, plan, ask_user, unload_model;\n")
30+ b.WriteString(" when the config turns them on: lsp, fetch_content, ask_agent, save_answer, search_memory, search_docs.\n")
31+ b.WriteString(" Plus the agent file's own tools (tools:) and MCP servers' tools.\n")
32+ b.WriteString("Model servers: Docker Model Runner, llmman, Ollama, llama.cpp — anything speaking the OpenAI API.\n\n")
3133 fmt.Fprintf(&b, "%s\n", Home)
3234 b.WriteString("Docs: docs/en (English), docs/fr (français) · mm -h for the flags")
3335 return b.String()
@@ -26,8 +26,10 @@ func About() string {
26 b.WriteString(" mm [config.yaml] a terminal REPL, line by line\n")26 b.WriteString(" mm [config.yaml] a terminal REPL, line by line\n")
27 b.WriteString(" mm -tui [config.yaml] one terminal window: conversation, input box, status bar\n")27 b.WriteString(" mm -tui [config.yaml] one terminal window: conversation, input box, status bar\n")
28 b.WriteString(" mm -acp the Agent Client Protocol on stdio, for Zed and other editors\n\n")28 b.WriteString(" mm -acp the Agent Client Protocol on stdio, for Zed and other editors\n\n")
29- b.WriteString("Tools the model can call: bash, read_skill, read_file, write_file, edit_file, lsp.\n")29+ b.WriteString("Built-in tools: bash, read_file, write_file, edit_file, read_skill, plan, ask_user, unload_model;\n")
30- b.WriteString("Model servers: Docker Model Runner, llama.cpp — anything speaking the OpenAI API.\n\n")30+ b.WriteString(" when the config turns them on: lsp, fetch_content, ask_agent, save_answer, search_memory, search_docs.\n")
31+ b.WriteString(" Plus the agent file's own tools (tools:) and MCP servers' tools.\n")
32+ b.WriteString("Model servers: Docker Model Runner, llmman, Ollama, llama.cpp — anything speaking the OpenAI API.\n\n")
31 fmt.Fprintf(&b, "%s\n", Home)33 fmt.Fprintf(&b, "%s\n", Home)
32 b.WriteString("Docs: docs/en (English), docs/fr (français) · mm -h for the flags")34 b.WriteString("Docs: docs/en (English), docs/fr (français) · mm -h for the flags")
33 return b.String()35 return b.String()
modified release-notes/v0.8.0.en.md +1 -0
@@ -47,6 +47,7 @@ Search what was said before and what is written down: recall your earlier questi
4747 ## Fixed
4848
4949 - **Line mode: only the first of several piped lines was read** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` or `mm agent.yaml < commands.txt` ran the first line and silently dropped the others. All of them now run, and a last line without a newline is no longer lost. Typing at the keyboard was not affected.
50+- **`mm --about` listed only six tools and two model servers.** It now lists every built-in tool — the ones on by default and the ones the configuration turns on — and the four servers (Docker Model Runner, llmman, Ollama, llama.cpp). A test now fails if a new built-in tool is missing from it.
5051
5152 ## Demos
5253
@@ -47,6 +47,7 @@ Search what was said before and what is written down: recall your earlier questi
47 ## Fixed47 ## Fixed
48 48
49 - **Line mode: only the first of several piped lines was read** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` or `mm agent.yaml < commands.txt` ran the first line and silently dropped the others. All of them now run, and a last line without a newline is no longer lost. Typing at the keyboard was not affected.49 - **Line mode: only the first of several piped lines was read** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` or `mm agent.yaml < commands.txt` ran the first line and silently dropped the others. All of them now run, and a last line without a newline is no longer lost. Typing at the keyboard was not affected.
50+- **`mm --about` listed only six tools and two model servers.** It now lists every built-in tool — the ones on by default and the ones the configuration turns on — and the four servers (Docker Model Runner, llmman, Ollama, llama.cpp). A test now fails if a new built-in tool is missing from it.
50 51
51 ## Demos52 ## Demos
52 53
modified release-notes/v0.8.0.fr.md +1 -0
@@ -47,6 +47,7 @@ Retrouver ce qui a été dit et ce qui est écrit : rappeler ses questions préc
4747 ## Corrigé
4848
4949 - **Mode ligne : seule la première de plusieurs lignes reçues par un pipe était lue** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` ou `mm agent.yaml < commandes.txt` exécutait la première ligne et ignorait les autres sans rien dire. Elles s'exécutent maintenant toutes, et une dernière ligne sans retour à la ligne n'est plus perdue. La saisie au clavier n'était pas touchée.
50+- **`mm --about` ne listait que six outils et deux serveurs de modèles.** Il liste maintenant tous les outils intégrés — ceux actifs par défaut et ceux que la configuration active — et les quatre serveurs (Docker Model Runner, llmman, Ollama, llama.cpp). Un test échoue désormais si un nouvel outil intégré y manque.
5051
5152 ## Démos
5253
@@ -47,6 +47,7 @@ Retrouver ce qui a été dit et ce qui est écrit : rappeler ses questions préc
47 ## Corrigé47 ## Corrigé
48 48
49 - **Mode ligne : seule la première de plusieurs lignes reçues par un pipe était lue** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` ou `mm agent.yaml < commandes.txt` exécutait la première ligne et ignorait les autres sans rien dire. Elles s'exécutent maintenant toutes, et une dernière ligne sans retour à la ligne n'est plus perdue. La saisie au clavier n'était pas touchée.49 - **Mode ligne : seule la première de plusieurs lignes reçues par un pipe était lue** (#103). `printf '/sessions\n/help\n' | mm agent.yaml` ou `mm agent.yaml < commandes.txt` exécutait la première ligne et ignorait les autres sans rien dire. Elles s'exécutent maintenant toutes, et une dernière ligne sans retour à la ligne n'est plus perdue. La saisie au clavier n'était pas touchée.
50+- **`mm --about` ne listait que six outils et deux serveurs de modèles.** Il liste maintenant tous les outils intégrés — ceux actifs par défaut et ceux que la configuration active — et les quatre serveurs (Docker Model Runner, llmman, Ollama, llama.cpp). Un test échoue désormais si un nouvel outil intégré y manque.
50 51
51 ## Démos52 ## Démos
52 53
added tools_reference_test.go +26 -0
new file mode 100644
@@ -0,0 +1,26 @@
1+package main
2+
3+import (
4+ "os"
5+ "strings"
6+ "testing"
7+
8+ "mm/internal/config"
9+)
10+
11+// Every built-in tool has its own section in the tools reference, in both
12+// languages — `plan` went without one for months, documented only in a
13+// how-to.
14+func TestEveryBuiltinToolHasAReferenceSection(t *testing.T) {
15+ for _, page := range []string{"docs/en/reference/tools.md", "docs/fr/reference/tools.md"} {
16+ data, err := os.ReadFile(page)
17+ if err != nil {
18+ t.Fatal(err)
19+ }
20+ for _, name := range config.BuiltinToolNames {
21+ if !strings.Contains(string(data), "\n## `"+name+"`\n") {
22+ t.Errorf("%s has no \"## `%s`\" section", page, name)
23+ }
24+ }
25+ }
26+}
new file mode 100644
@@ -0,0 +1,26 @@
1+package main
2+
3+import (
4+ "os"
5+ "strings"
6+ "testing"
7+
8+ "mm/internal/config"
9+)
10+
11+// Every built-in tool has its own section in the tools reference, in both
12+// languages — `plan` went without one for months, documented only in a
13+// how-to.
14+func TestEveryBuiltinToolHasAReferenceSection(t *testing.T) {
15+ for _, page := range []string{"docs/en/reference/tools.md", "docs/fr/reference/tools.md"} {
16+ data, err := os.ReadFile(page)
17+ if err != nil {
18+ t.Fatal(err)
19+ }
20+ for _, name := range config.BuiltinToolNames {
21+ if !strings.Contains(string(data), "\n## `"+name+"`\n") {
22+ t.Errorf("%s has no \"## `%s`\" section", page, name)
23+ }
24+ }
25+ }
26+}