🛟 Updated.Unverified
fefc32c parent: 3e8d3b0 renamed
.tickets/issues/0080-structured-output-from-go-type-declarations-in-t.yaml +5 -5 | similarity index 95% | ||
| rename from .tickets/issues/0080-structured-output-from-go-type-declarations.yaml | ||
| rename to .tickets/issues/0080-structured-output-from-go-type-declarations-in-t.yaml | ||
| @@ -1,12 +1,12 @@ | ||
| 1 | 1 | # Managed by IssueSpec. Hand edits are welcome; keep the schema valid. |
| 2 | 2 | id: 80 |
| 3 | 3 | title: Structured output from Go type declarations in the config |
| 4 | -state: open | |
| 4 | +state: closed | |
| 5 | 5 | author: |
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-10-08T10:00:00.000Z |
| 9 | -updatedAt: 2026-10-08T16:00:00.000Z | |
| 9 | +updatedAt: 2026-10-09T03:16:38.703Z | |
| 10 | 10 | labels: |
| 11 | 11 | - enhancement |
| 12 | 12 | epic: Models |
| @@ -88,7 +88,7 @@ tasks: | ||
| 88 | 88 | updatedAt: 2026-10-08T16:00:00.000Z |
| 89 | 89 | closedAt: 2026-10-08T16:00:00.000Z |
| 90 | 90 | - id: 4 |
| 91 | - title: "Refuse structuredOutput when the agent declares any tool (config error listing the tools)" | |
| 91 | + title: Refuse structuredOutput when the agent declares any tool (config error listing the tools) | |
| 92 | 92 | state: closed |
| 93 | 93 | priority: none |
| 94 | 94 | author: |
| @@ -108,7 +108,7 @@ tasks: | ||
| 108 | 108 | updatedAt: 2026-10-08T16:00:00.000Z |
| 109 | 109 | closedAt: 2026-10-08T16:00:00.000Z |
| 110 | 110 | - id: 6 |
| 111 | - title: "Quality gate (quality skill)" | |
| 111 | + title: Quality gate (quality skill) | |
| 112 | 112 | state: open |
| 113 | 113 | priority: none |
| 114 | 114 | author: |
| @@ -116,7 +116,7 @@ tasks: | ||
| 116 | 116 | email: ph.charriere@gmail.com |
| 117 | 117 | createdAt: 2026-10-08T10:00:00.000Z |
| 118 | 118 | - id: 7 |
| 119 | - title: "Docs EN+FR (how-to + reference/configuration) and .memory update" | |
| 119 | + title: Docs EN+FR (how-to + reference/configuration) and .memory update | |
| 120 | 120 | state: closed |
| 121 | 121 | priority: none |
| 122 | 122 | author: |
| similarity index 95% | |||
| rename from .tickets/issues/0080-structured-output-from-go-type-declarations.yaml | |||
| rename to .tickets/issues/0080-structured-output-from-go-type-declarations-in-t.yaml | |||
| @@ -1,12 +1,12 @@ | |||
| 1 | # Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | 1 | # Managed by IssueSpec. Hand edits are welcome; keep the schema valid. |
| 2 | id: 80 | 2 | id: 80 |
| 3 | title: Structured output from Go type declarations in the config | 3 | title: Structured output from Go type declarations in the config |
| 4 | -state: open | 4 | +state: closed |
| 5 | author: | 5 | author: |
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-10-08T10:00:00.000Z | 8 | createdAt: 2026-10-08T10:00:00.000Z |
| 9 | -updatedAt: 2026-10-08T16:00:00.000Z | 9 | +updatedAt: 2026-10-09T03:16:38.703Z |
| 10 | labels: | 10 | labels: |
| 11 | - enhancement | 11 | - enhancement |
| 12 | epic: Models | 12 | epic: Models |
| @@ -88,7 +88,7 @@ tasks: | |||
| 88 | updatedAt: 2026-10-08T16:00:00.000Z | 88 | updatedAt: 2026-10-08T16:00:00.000Z |
| 89 | closedAt: 2026-10-08T16:00:00.000Z | 89 | closedAt: 2026-10-08T16:00:00.000Z |
| 90 | - id: 4 | 90 | - id: 4 |
| 91 | - title: "Refuse structuredOutput when the agent declares any tool (config error listing the tools)" | 91 | + title: Refuse structuredOutput when the agent declares any tool (config error listing the tools) |
| 92 | state: closed | 92 | state: closed |
| 93 | priority: none | 93 | priority: none |
| 94 | author: | 94 | author: |
| @@ -108,7 +108,7 @@ tasks: | |||
| 108 | updatedAt: 2026-10-08T16:00:00.000Z | 108 | updatedAt: 2026-10-08T16:00:00.000Z |
| 109 | closedAt: 2026-10-08T16:00:00.000Z | 109 | closedAt: 2026-10-08T16:00:00.000Z |
| 110 | - id: 6 | 110 | - id: 6 |
| 111 | - title: "Quality gate (quality skill)" | 111 | + title: Quality gate (quality skill) |
| 112 | state: open | 112 | state: open |
| 113 | priority: none | 113 | priority: none |
| 114 | author: | 114 | author: |
| @@ -116,7 +116,7 @@ tasks: | |||
| 116 | email: ph.charriere@gmail.com | 116 | email: ph.charriere@gmail.com |
| 117 | createdAt: 2026-10-08T10:00:00.000Z | 117 | createdAt: 2026-10-08T10:00:00.000Z |
| 118 | - id: 7 | 118 | - id: 7 |
| 119 | - title: "Docs EN+FR (how-to + reference/configuration) and .memory update" | 119 | + title: Docs EN+FR (how-to + reference/configuration) and .memory update |
| 120 | state: closed | 120 | state: closed |
| 121 | priority: none | 121 | priority: none |
| 122 | author: | 122 | author: |
added
.tickets/issues/0081-support-agents-md-project-instructions-for-the-a.yaml +155 -0 | new file mode 100644 | ||
| @@ -0,0 +1,155 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 81 | |
| 3 | +title: Support AGENTS.md (project instructions for the agent) | |
| 4 | +state: open | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-10-09T08:00:00.000Z | |
| 9 | +updatedAt: 2026-10-09T15:00:00.000Z | |
| 10 | +labels: | |
| 11 | + - enhancement | |
| 12 | +epic: Agent Memory | |
| 13 | +related: | |
| 14 | + - 79 | |
| 15 | +body: | | |
| 16 | + ## Goal | |
| 17 | + | |
| 18 | + When mm works in a project that has an `AGENTS.md` file, read it and give its content to the model as project instructions: build/test commands, conventions, what not to touch. Today the only instructions are the config's `system` prompt, the same for every project. | |
| 19 | + | |
| 20 | + `AGENTS.md` (https://agents.md) is a shared convention among coding agents: a plain Markdown file at the repository root, no required fields. In a monorepo, nested `AGENTS.md` files can sit in sub-directories; the one closest to the file being worked on takes precedence, and an explicit instruction from the user in the chat wins over the file. | |
| 21 | + | |
| 22 | + ## Where it plugs in (2026-10-09) | |
| 23 | + | |
| 24 | + - The system message is built in one place: `session.Fresh(system)` (`internal/session/session.go`), called by the line REPL (`internal/agent/agent.go`), `-p` (`RunPrompt`, `RunStructuredPrompt`), the window (`internal/tui/turn.go`, `commands.go` for `/new`), and the ACP façade. | |
| 25 | + - The working directory differs per front end: the process cwd in the terminal, the session's `cwd` in ACP (`ui.Resolve` / the sink's `WorkDir`, as for `sessions.dir` and `trace.dir`). | |
| 26 | + - A `/new`, a `/resume` and a context compression must keep the instructions (they live in the system message, which compression already keeps). | |
| 27 | + | |
| 28 | + ## Decided (2026-10-09, with the user) | |
| 29 | + | |
| 30 | + How the file joins the prompt: | |
| 31 | + | |
| 32 | + - **Appended to `system`**, after the config's own prompt, under a heading naming its origin — `PROJECT INSTRUCTIONS (from <absolute path>)` — never in place of it: `system` says what the agent is, AGENTS.md what the project is. Not a first user message: small models weigh the system message more, and Gemma/Mistral templates refuse two consecutive user turns. Same mechanism as `structuredOutput`'s `OUTPUT FORMAT` paragraph (ticket 80). | |
| 33 | + - **Read at start-up, then again at every `/new` and every new ACP session** — never in the middle of a conversation: a system message that changes mid-way breaks the server's prompt cache (llama.cpp re-reads the whole history; see ticket 53). An edited file is picked up by the next conversation. | |
| 34 | + - Still to settle in the implementation: `/resume` — keep the stored system message, or replace it with the current file (ACP already has `history.WithSystem` for a reloaded session). Compression already keeps the system message as is. | |
| 35 | + - **Size: a warning, not a cut.** The banner says the file's estimated size and its share of the context window (`AGENTS.md: ~6200 tokens, 40 % of the 16k window`) when it is large. An optional cap (e.g. `agentsMd.maxChars`, off by default) may be added; beyond it the file is not truncated silently — truncation can drop the one rule that matters, at the end of the file. | |
| 36 | + | |
| 37 | + Answered by the user in comment 1 (2026-10-09): | |
| 38 | + | |
| 39 | + - **Only `AGENTS.md`** — no `CLAUDE.md` / `.github/copilot-instructions.md` fallback. | |
| 40 | + - **The working directory only** — no walk up to the git root, no nested files concatenated. (Working directory = the process cwd in the terminal and `-p`, the session's `cwd` over ACP.) | |
| 41 | + - **A switch, on by default, with a path override.** | |
| 42 | + - **An asked agent** (`ask_agent`, `/ask`, `mm --ask`) reads it only if ITS OWN config has the switch on — so, the switch being on by default, an asked agent reads the AGENTS.md of the working directory it is started in unless its file says `false`. | |
| 43 | + - **Skipped for structured agents** (`structuredOutput` set, ticket 80): prose instructions would fight the JSON constraint. | |
| 44 | + | |
| 45 | + Settled with the user on 2026-10-09 (the last open points): | |
| 46 | + | |
| 47 | + - **Config shape: a block**, like `trace` and `sessions` — | |
| 48 | + `agentsMd: {enabled: true, path: AGENTS.md}` (enabled by default). A relative `path` is resolved against the WORKING DIRECTORY (the session's cwd over ACP), like `sessions.dir` and `trace.dir` — not against the config file: one `agent.yaml` serves many projects. The optional cap, if added, goes in the same block (`agentsMd.maxChars`, 0 = none). | |
| 49 | + - **`/resume` re-reads the file** and the resumed conversation continues under the current one — the rule already in force: `session.WithSystem` (`internal/session/session.go`) replaces a stored system message with the agent's current prompt, in `store/keeper.go` (terminal, window) and `acp/persist.go` (session/load). No cache cost: a resumed session is re-read whole by the server anyway. | |
| 50 | + - **Beyond the optional cap: the file is SKIPPED**, with a banner warning (`AGENTS.md skipped: <n> characters > agentsMd.maxChars <cap>`) — never truncated: a missing file is a visible state, a half-read one hides the rule that was cut. | |
| 51 | + - **A structured agent that skips an existing AGENTS.md says so neutrally in the banner** (`agents.md: ignored (structured output)`, only when a file exists), not as a warning — the same spirit as `tools: none (structured output: JSON answers)`. No banner in `-p` / `--ask`, so nothing there. | |
| 52 | + | |
| 53 | + No question left open. | |
| 54 | + | |
| 55 | + ## Implemented (2026-10-09, branch `feature/agents-file`) | |
| 56 | + | |
| 57 | + - `internal/agentsmd`: `Load(workDir)` → Disabled / Missing / Loaded / Skipped / Ignored / Unreadable; `System(base, workDir)`. | |
| 58 | + - Wired wherever a conversation starts: line mode (start, /new, /resume, -p), window (start, /new, /resume), ACP (session/new with its cwd, /new, session/load). | |
| 59 | + - Banner column `agents.md: …` + warnings (≥ 20 % of a known window, skipped, unreadable) — `agentsmd_banner.go`. | |
| 60 | + - Live on llmman (Qwen3.5-4B): the model followed a test AGENTS.md (`make check`, a signature); an agent asked with `--ask` read it with its switch on and not with `enabled: false`; a structured agent ignored it and its JSON stayed clean. | |
| 61 | + - Task 6 (quality gate) open: qlty is not installed in the sandbox. | |
| 62 | +tasks: | |
| 63 | + - id: 1 | |
| 64 | + title: Decide the open questions below with the user (which files, where to look, how it joins the system prompt, a switch to turn it off) | |
| 65 | + state: closed | |
| 66 | + priority: none | |
| 67 | + author: | |
| 68 | + name: k33g | |
| 69 | + email: ph.charriere@gmail.com | |
| 70 | + createdAt: 2026-10-09T08:00:00.000Z | |
| 71 | + updatedAt: 2026-10-09T15:00:00.000Z | |
| 72 | + closedAt: 2026-10-09T15:00:00.000Z | |
| 73 | + - id: 2 | |
| 74 | + title: "Failing tests first: an AGENTS.md in the working directory reaches the system message; none = system prompt unchanged" | |
| 75 | + state: closed | |
| 76 | + priority: none | |
| 77 | + author: | |
| 78 | + name: k33g | |
| 79 | + email: ph.charriere@gmail.com | |
| 80 | + createdAt: 2026-10-09T08:00:00.000Z | |
| 81 | + updatedAt: 2026-10-09T15:00:00.000Z | |
| 82 | + closedAt: 2026-10-09T15:00:00.000Z | |
| 83 | + - id: 3 | |
| 84 | + title: Read AGENTS.md from the session's working directory in every front end (terminal, window, -p, ACP session cwd, an agent asked through ask_agent / --ask) | |
| 85 | + state: closed | |
| 86 | + priority: none | |
| 87 | + author: | |
| 88 | + name: k33g | |
| 89 | + email: ph.charriere@gmail.com | |
| 90 | + createdAt: 2026-10-09T08:00:00.000Z | |
| 91 | + updatedAt: 2026-10-09T15:00:00.000Z | |
| 92 | + closedAt: 2026-10-09T15:00:00.000Z | |
| 93 | + - id: 4 | |
| 94 | + title: "Banner / ACP: say which instruction file(s) were loaded, and warn when one is too big" | |
| 95 | + state: closed | |
| 96 | + priority: none | |
| 97 | + author: | |
| 98 | + name: k33g | |
| 99 | + email: ph.charriere@gmail.com | |
| 100 | + createdAt: 2026-10-09T08:00:00.000Z | |
| 101 | + updatedAt: 2026-10-09T15:00:00.000Z | |
| 102 | + closedAt: 2026-10-09T15:00:00.000Z | |
| 103 | + - id: 5 | |
| 104 | + title: Live check on llmman with a demo project holding an AGENTS.md | |
| 105 | + state: closed | |
| 106 | + priority: none | |
| 107 | + author: | |
| 108 | + name: k33g | |
| 109 | + email: ph.charriere@gmail.com | |
| 110 | + createdAt: 2026-10-09T08:00:00.000Z | |
| 111 | + updatedAt: 2026-10-09T15:00:00.000Z | |
| 112 | + closedAt: 2026-10-09T15:00:00.000Z | |
| 113 | + - id: 6 | |
| 114 | + title: Quality gate (quality skill) | |
| 115 | + state: open | |
| 116 | + priority: none | |
| 117 | + author: | |
| 118 | + name: k33g | |
| 119 | + email: ph.charriere@gmail.com | |
| 120 | + createdAt: 2026-10-09T08:00:00.000Z | |
| 121 | + - id: 7 | |
| 122 | + title: Docs EN+FR (how-to + reference/configuration + explanation) and .memory update | |
| 123 | + state: closed | |
| 124 | + priority: none | |
| 125 | + author: | |
| 126 | + name: k33g | |
| 127 | + email: ph.charriere@gmail.com | |
| 128 | + createdAt: 2026-10-09T08:00:00.000Z | |
| 129 | + updatedAt: 2026-10-09T15:00:00.000Z | |
| 130 | + closedAt: 2026-10-09T15:00:00.000Z | |
| 131 | +comments: | |
| 132 | + - id: 1 | |
| 133 | + author: | |
| 134 | + name: k33g | |
| 135 | + email: ph.charriere@gmail.com | |
| 136 | + createdAt: 2026-10-09T03:27:47.320Z | |
| 137 | + body: |+ | |
| 138 | + ## Answers to open questions: | |
| 139 | + | |
| 140 | + - Which files: `AGENTS.md` only, or also `CLAUDE.md` / `.github/copilot-instructions.md` as fallbacks? | |
| 141 | + - Only `AGENTS.md` | |
| 142 | + | |
| 143 | + - Where to look: the working directory only, or walk up to the git root and concatenate root → cwd (nested files)? | |
| 144 | + - the working directory only | |
| 145 | + | |
| 146 | + - A switch: `agentsMd: true|false` in the config (on by default?), and a path override? | |
| 147 | + - yes and yes and yes | |
| 148 | + | |
| 149 | + - An agent asked through `ask_agent` / `mm --ask`: does it read the AGENTS.md of its asker's project (same cwd), or none? | |
| 150 | + - Only if agentsMd: true in the asked agent config file | |
| 151 | + | |
| 152 | + - Interaction with `structuredOutput` (ticket 80): an instruction file asking for prose would fight the JSON constraint — skip it for structured agents? | |
| 153 | + - skip it for structured agents | |
| 154 | + | |
| 155 | + | |
| new file mode 100644 | |||
| @@ -0,0 +1,155 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 81 | ||
| 3 | +title: Support AGENTS.md (project instructions for the agent) | ||
| 4 | +state: open | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-10-09T08:00:00.000Z | ||
| 9 | +updatedAt: 2026-10-09T15:00:00.000Z | ||
| 10 | +labels: | ||
| 11 | + - enhancement | ||
| 12 | +epic: Agent Memory | ||
| 13 | +related: | ||
| 14 | + - 79 | ||
| 15 | +body: | | ||
| 16 | + ## Goal | ||
| 17 | + | ||
| 18 | + When mm works in a project that has an `AGENTS.md` file, read it and give its content to the model as project instructions: build/test commands, conventions, what not to touch. Today the only instructions are the config's `system` prompt, the same for every project. | ||
| 19 | + | ||
| 20 | + `AGENTS.md` (https://agents.md) is a shared convention among coding agents: a plain Markdown file at the repository root, no required fields. In a monorepo, nested `AGENTS.md` files can sit in sub-directories; the one closest to the file being worked on takes precedence, and an explicit instruction from the user in the chat wins over the file. | ||
| 21 | + | ||
| 22 | + ## Where it plugs in (2026-10-09) | ||
| 23 | + | ||
| 24 | + - The system message is built in one place: `session.Fresh(system)` (`internal/session/session.go`), called by the line REPL (`internal/agent/agent.go`), `-p` (`RunPrompt`, `RunStructuredPrompt`), the window (`internal/tui/turn.go`, `commands.go` for `/new`), and the ACP façade. | ||
| 25 | + - The working directory differs per front end: the process cwd in the terminal, the session's `cwd` in ACP (`ui.Resolve` / the sink's `WorkDir`, as for `sessions.dir` and `trace.dir`). | ||
| 26 | + - A `/new`, a `/resume` and a context compression must keep the instructions (they live in the system message, which compression already keeps). | ||
| 27 | + | ||
| 28 | + ## Decided (2026-10-09, with the user) | ||
| 29 | + | ||
| 30 | + How the file joins the prompt: | ||
| 31 | + | ||
| 32 | + - **Appended to `system`**, after the config's own prompt, under a heading naming its origin — `PROJECT INSTRUCTIONS (from <absolute path>)` — never in place of it: `system` says what the agent is, AGENTS.md what the project is. Not a first user message: small models weigh the system message more, and Gemma/Mistral templates refuse two consecutive user turns. Same mechanism as `structuredOutput`'s `OUTPUT FORMAT` paragraph (ticket 80). | ||
| 33 | + - **Read at start-up, then again at every `/new` and every new ACP session** — never in the middle of a conversation: a system message that changes mid-way breaks the server's prompt cache (llama.cpp re-reads the whole history; see ticket 53). An edited file is picked up by the next conversation. | ||
| 34 | + - Still to settle in the implementation: `/resume` — keep the stored system message, or replace it with the current file (ACP already has `history.WithSystem` for a reloaded session). Compression already keeps the system message as is. | ||
| 35 | + - **Size: a warning, not a cut.** The banner says the file's estimated size and its share of the context window (`AGENTS.md: ~6200 tokens, 40 % of the 16k window`) when it is large. An optional cap (e.g. `agentsMd.maxChars`, off by default) may be added; beyond it the file is not truncated silently — truncation can drop the one rule that matters, at the end of the file. | ||
| 36 | + | ||
| 37 | + Answered by the user in comment 1 (2026-10-09): | ||
| 38 | + | ||
| 39 | + - **Only `AGENTS.md`** — no `CLAUDE.md` / `.github/copilot-instructions.md` fallback. | ||
| 40 | + - **The working directory only** — no walk up to the git root, no nested files concatenated. (Working directory = the process cwd in the terminal and `-p`, the session's `cwd` over ACP.) | ||
| 41 | + - **A switch, on by default, with a path override.** | ||
| 42 | + - **An asked agent** (`ask_agent`, `/ask`, `mm --ask`) reads it only if ITS OWN config has the switch on — so, the switch being on by default, an asked agent reads the AGENTS.md of the working directory it is started in unless its file says `false`. | ||
| 43 | + - **Skipped for structured agents** (`structuredOutput` set, ticket 80): prose instructions would fight the JSON constraint. | ||
| 44 | + | ||
| 45 | + Settled with the user on 2026-10-09 (the last open points): | ||
| 46 | + | ||
| 47 | + - **Config shape: a block**, like `trace` and `sessions` — | ||
| 48 | + `agentsMd: {enabled: true, path: AGENTS.md}` (enabled by default). A relative `path` is resolved against the WORKING DIRECTORY (the session's cwd over ACP), like `sessions.dir` and `trace.dir` — not against the config file: one `agent.yaml` serves many projects. The optional cap, if added, goes in the same block (`agentsMd.maxChars`, 0 = none). | ||
| 49 | + - **`/resume` re-reads the file** and the resumed conversation continues under the current one — the rule already in force: `session.WithSystem` (`internal/session/session.go`) replaces a stored system message with the agent's current prompt, in `store/keeper.go` (terminal, window) and `acp/persist.go` (session/load). No cache cost: a resumed session is re-read whole by the server anyway. | ||
| 50 | + - **Beyond the optional cap: the file is SKIPPED**, with a banner warning (`AGENTS.md skipped: <n> characters > agentsMd.maxChars <cap>`) — never truncated: a missing file is a visible state, a half-read one hides the rule that was cut. | ||
| 51 | + - **A structured agent that skips an existing AGENTS.md says so neutrally in the banner** (`agents.md: ignored (structured output)`, only when a file exists), not as a warning — the same spirit as `tools: none (structured output: JSON answers)`. No banner in `-p` / `--ask`, so nothing there. | ||
| 52 | + | ||
| 53 | + No question left open. | ||
| 54 | + | ||
| 55 | + ## Implemented (2026-10-09, branch `feature/agents-file`) | ||
| 56 | + | ||
| 57 | + - `internal/agentsmd`: `Load(workDir)` → Disabled / Missing / Loaded / Skipped / Ignored / Unreadable; `System(base, workDir)`. | ||
| 58 | + - Wired wherever a conversation starts: line mode (start, /new, /resume, -p), window (start, /new, /resume), ACP (session/new with its cwd, /new, session/load). | ||
| 59 | + - Banner column `agents.md: …` + warnings (≥ 20 % of a known window, skipped, unreadable) — `agentsmd_banner.go`. | ||
| 60 | + - Live on llmman (Qwen3.5-4B): the model followed a test AGENTS.md (`make check`, a signature); an agent asked with `--ask` read it with its switch on and not with `enabled: false`; a structured agent ignored it and its JSON stayed clean. | ||
| 61 | + - Task 6 (quality gate) open: qlty is not installed in the sandbox. | ||
| 62 | +tasks: | ||
| 63 | + - id: 1 | ||
| 64 | + title: Decide the open questions below with the user (which files, where to look, how it joins the system prompt, a switch to turn it off) | ||
| 65 | + state: closed | ||
| 66 | + priority: none | ||
| 67 | + author: | ||
| 68 | + name: k33g | ||
| 69 | + email: ph.charriere@gmail.com | ||
| 70 | + createdAt: 2026-10-09T08:00:00.000Z | ||
| 71 | + updatedAt: 2026-10-09T15:00:00.000Z | ||
| 72 | + closedAt: 2026-10-09T15:00:00.000Z | ||
| 73 | + - id: 2 | ||
| 74 | + title: "Failing tests first: an AGENTS.md in the working directory reaches the system message; none = system prompt unchanged" | ||
| 75 | + state: closed | ||
| 76 | + priority: none | ||
| 77 | + author: | ||
| 78 | + name: k33g | ||
| 79 | + email: ph.charriere@gmail.com | ||
| 80 | + createdAt: 2026-10-09T08:00:00.000Z | ||
| 81 | + updatedAt: 2026-10-09T15:00:00.000Z | ||
| 82 | + closedAt: 2026-10-09T15:00:00.000Z | ||
| 83 | + - id: 3 | ||
| 84 | + title: Read AGENTS.md from the session's working directory in every front end (terminal, window, -p, ACP session cwd, an agent asked through ask_agent / --ask) | ||
| 85 | + state: closed | ||
| 86 | + priority: none | ||
| 87 | + author: | ||
| 88 | + name: k33g | ||
| 89 | + email: ph.charriere@gmail.com | ||
| 90 | + createdAt: 2026-10-09T08:00:00.000Z | ||
| 91 | + updatedAt: 2026-10-09T15:00:00.000Z | ||
| 92 | + closedAt: 2026-10-09T15:00:00.000Z | ||
| 93 | + - id: 4 | ||
| 94 | + title: "Banner / ACP: say which instruction file(s) were loaded, and warn when one is too big" | ||
| 95 | + state: closed | ||
| 96 | + priority: none | ||
| 97 | + author: | ||
| 98 | + name: k33g | ||
| 99 | + email: ph.charriere@gmail.com | ||
| 100 | + createdAt: 2026-10-09T08:00:00.000Z | ||
| 101 | + updatedAt: 2026-10-09T15:00:00.000Z | ||
| 102 | + closedAt: 2026-10-09T15:00:00.000Z | ||
| 103 | + - id: 5 | ||
| 104 | + title: Live check on llmman with a demo project holding an AGENTS.md | ||
| 105 | + state: closed | ||
| 106 | + priority: none | ||
| 107 | + author: | ||
| 108 | + name: k33g | ||
| 109 | + email: ph.charriere@gmail.com | ||
| 110 | + createdAt: 2026-10-09T08:00:00.000Z | ||
| 111 | + updatedAt: 2026-10-09T15:00:00.000Z | ||
| 112 | + closedAt: 2026-10-09T15:00:00.000Z | ||
| 113 | + - id: 6 | ||
| 114 | + title: Quality gate (quality skill) | ||
| 115 | + state: open | ||
| 116 | + priority: none | ||
| 117 | + author: | ||
| 118 | + name: k33g | ||
| 119 | + email: ph.charriere@gmail.com | ||
| 120 | + createdAt: 2026-10-09T08:00:00.000Z | ||
| 121 | + - id: 7 | ||
| 122 | + title: Docs EN+FR (how-to + reference/configuration + explanation) and .memory update | ||
| 123 | + state: closed | ||
| 124 | + priority: none | ||
| 125 | + author: | ||
| 126 | + name: k33g | ||
| 127 | + email: ph.charriere@gmail.com | ||
| 128 | + createdAt: 2026-10-09T08:00:00.000Z | ||
| 129 | + updatedAt: 2026-10-09T15:00:00.000Z | ||
| 130 | + closedAt: 2026-10-09T15:00:00.000Z | ||
| 131 | +comments: | ||
| 132 | + - id: 1 | ||
| 133 | + author: | ||
| 134 | + name: k33g | ||
| 135 | + email: ph.charriere@gmail.com | ||
| 136 | + createdAt: 2026-10-09T03:27:47.320Z | ||
| 137 | + body: |+ | ||
| 138 | + ## Answers to open questions: | ||
| 139 | + | ||
| 140 | + - Which files: `AGENTS.md` only, or also `CLAUDE.md` / `.github/copilot-instructions.md` as fallbacks? | ||
| 141 | + - Only `AGENTS.md` | ||
| 142 | + | ||
| 143 | + - Where to look: the working directory only, or walk up to the git root and concatenate root → cwd (nested files)? | ||
| 144 | + - the working directory only | ||
| 145 | + | ||
| 146 | + - A switch: `agentsMd: true|false` in the config (on by default?), and a path override? | ||
| 147 | + - yes and yes and yes | ||
| 148 | + | ||
| 149 | + - An agent asked through `ask_agent` / `mm --ask`: does it read the AGENTS.md of its asker's project (same cwd), or none? | ||
| 150 | + - Only if agentsMd: true in the asked agent config file | ||
| 151 | + | ||
| 152 | + - Interaction with `structuredOutput` (ticket 80): an instruction file asking for prose would fight the JSON constraint — skip it for structured agents? | ||
| 153 | + - skip it for structured agents | ||
| 154 | + | ||
| 155 | + | ||
added
agentsmd_banner.go +57 -0 | new file mode 100644 | ||
| @@ -0,0 +1,57 @@ | ||
| 1 | +package main | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "fmt" | |
| 5 | + | |
| 6 | + "mm/internal/agentsmd" | |
| 7 | + "mm/internal/config" | |
| 8 | +) | |
| 9 | + | |
| 10 | +// largeAgentsMdShare is the share of the context window (in percent) from | |
| 11 | +// which the banner warns about the size of AGENTS.md: the file rides in the | |
| 12 | +// system message of every request, for the whole conversation. | |
| 13 | +const largeAgentsMdShare = 20 | |
| 14 | + | |
| 15 | +// agentsMdBanner returns the banner's `agents.md:` column (with its leading | |
| 16 | +// " | ", or "" when there is nothing to say) and the warnings about the file. | |
| 17 | +// window is the context window in tokens, 0 when unknown. In ACP mode each | |
| 18 | +// session reads the file of its own cwd, which no session has given yet: the | |
| 19 | +// column only says where it will be looked for. | |
| 20 | +// | |
| 21 | +// col, warnings := agentsMdBanner(false, 16384) | |
| 22 | +// // " | agents.md: /home/me/project/AGENTS.md (~350 tokens)", nil | |
| 23 | +func agentsMdBanner(acpMode bool, window int) (string, []string) { | |
| 24 | + settings := config.Cfg.AgentsMd | |
| 25 | + if !settings.Enabled { | |
| 26 | + return "", nil | |
| 27 | + } | |
| 28 | + if acpMode { | |
| 29 | + return " | agents.md: <session cwd>/" + settings.Path, nil | |
| 30 | + } | |
| 31 | + f := agentsmd.Load("") | |
| 32 | + switch f.Status { | |
| 33 | + case agentsmd.Loaded: | |
| 34 | + return fmt.Sprintf(" | agents.md: %s (~%d tokens)", f.Path, f.Tokens()), largeAgentsMdWarning(f, window) | |
| 35 | + case agentsmd.Ignored: | |
| 36 | + // Neutral, not a warning: a structured agent skips it on purpose. | |
| 37 | + return " | agents.md: ignored (structured output)", nil | |
| 38 | + case agentsmd.Skipped: | |
| 39 | + return " | agents.md: skipped (too long)", []string{fmt.Sprintf( | |
| 40 | + "%s skipped: %d characters > agentsMd.maxChars %d — it is not read at all (shorten it, or raise the limit)", | |
| 41 | + f.Path, f.Chars(), settings.MaxChars)} | |
| 42 | + case agentsmd.Unreadable: | |
| 43 | + return "", []string{fmt.Sprintf("%s cannot be read: %v — no project instructions", f.Path, f.Err)} | |
| 44 | + } | |
| 45 | + return "", nil | |
| 46 | +} | |
| 47 | + | |
| 48 | +// largeAgentsMdWarning says when the file takes a large share of a known | |
| 49 | +// context window. Nothing is cut: the user decides (agentsMd.maxChars). | |
| 50 | +func largeAgentsMdWarning(f agentsmd.File, window int) []string { | |
| 51 | + if window <= 0 || f.Tokens()*100 < window*largeAgentsMdShare { | |
| 52 | + return nil | |
| 53 | + } | |
| 54 | + return []string{fmt.Sprintf( | |
| 55 | + "%s is ~%d tokens, %d %% of the %d-token context window — every request carries it (shorten it, or set agentsMd.maxChars)", | |
| 56 | + f.Path, f.Tokens(), f.Tokens()*100/window, window)} | |
| 57 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | +package main | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "fmt" | ||
| 5 | + | ||
| 6 | + "mm/internal/agentsmd" | ||
| 7 | + "mm/internal/config" | ||
| 8 | +) | ||
| 9 | + | ||
| 10 | +// largeAgentsMdShare is the share of the context window (in percent) from | ||
| 11 | +// which the banner warns about the size of AGENTS.md: the file rides in the | ||
| 12 | +// system message of every request, for the whole conversation. | ||
| 13 | +const largeAgentsMdShare = 20 | ||
| 14 | + | ||
| 15 | +// agentsMdBanner returns the banner's `agents.md:` column (with its leading | ||
| 16 | +// " | ", or "" when there is nothing to say) and the warnings about the file. | ||
| 17 | +// window is the context window in tokens, 0 when unknown. In ACP mode each | ||
| 18 | +// session reads the file of its own cwd, which no session has given yet: the | ||
| 19 | +// column only says where it will be looked for. | ||
| 20 | +// | ||
| 21 | +// col, warnings := agentsMdBanner(false, 16384) | ||
| 22 | +// // " | agents.md: /home/me/project/AGENTS.md (~350 tokens)", nil | ||
| 23 | +func agentsMdBanner(acpMode bool, window int) (string, []string) { | ||
| 24 | + settings := config.Cfg.AgentsMd | ||
| 25 | + if !settings.Enabled { | ||
| 26 | + return "", nil | ||
| 27 | + } | ||
| 28 | + if acpMode { | ||
| 29 | + return " | agents.md: <session cwd>/" + settings.Path, nil | ||
| 30 | + } | ||
| 31 | + f := agentsmd.Load("") | ||
| 32 | + switch f.Status { | ||
| 33 | + case agentsmd.Loaded: | ||
| 34 | + return fmt.Sprintf(" | agents.md: %s (~%d tokens)", f.Path, f.Tokens()), largeAgentsMdWarning(f, window) | ||
| 35 | + case agentsmd.Ignored: | ||
| 36 | + // Neutral, not a warning: a structured agent skips it on purpose. | ||
| 37 | + return " | agents.md: ignored (structured output)", nil | ||
| 38 | + case agentsmd.Skipped: | ||
| 39 | + return " | agents.md: skipped (too long)", []string{fmt.Sprintf( | ||
| 40 | + "%s skipped: %d characters > agentsMd.maxChars %d — it is not read at all (shorten it, or raise the limit)", | ||
| 41 | + f.Path, f.Chars(), settings.MaxChars)} | ||
| 42 | + case agentsmd.Unreadable: | ||
| 43 | + return "", []string{fmt.Sprintf("%s cannot be read: %v — no project instructions", f.Path, f.Err)} | ||
| 44 | + } | ||
| 45 | + return "", nil | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +// largeAgentsMdWarning says when the file takes a large share of a known | ||
| 49 | +// context window. Nothing is cut: the user decides (agentsMd.maxChars). | ||
| 50 | +func largeAgentsMdWarning(f agentsmd.File, window int) []string { | ||
| 51 | + if window <= 0 || f.Tokens()*100 < window*largeAgentsMdShare { | ||
| 52 | + return nil | ||
| 53 | + } | ||
| 54 | + return []string{fmt.Sprintf( | ||
| 55 | + "%s is ~%d tokens, %d %% of the %d-token context window — every request carries it (shorten it, or set agentsMd.maxChars)", | ||
| 56 | + f.Path, f.Tokens(), f.Tokens()*100/window, window)} | ||
| 57 | +} | ||
added
agentsmd_banner_test.go +89 -0 | new file mode 100644 | ||
| @@ -0,0 +1,89 @@ | ||
| 1 | +package main | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "os" | |
| 5 | + "path/filepath" | |
| 6 | + "strings" | |
| 7 | + "testing" | |
| 8 | + | |
| 9 | + "mm/internal/config" | |
| 10 | +) | |
| 11 | + | |
| 12 | +// inProject runs the test in a fresh directory holding an AGENTS.md of text | |
| 13 | +// ("" = no file), with the given settings. | |
| 14 | +func inProject(t *testing.T, text string, settings config.AgentsMdConfig) string { | |
| 15 | + t.Helper() | |
| 16 | + saved, savedSchema := config.Cfg.AgentsMd, config.Cfg.OutputSchema | |
| 17 | + config.Cfg.AgentsMd, config.Cfg.OutputSchema = settings, nil | |
| 18 | + t.Cleanup(func() { config.Cfg.AgentsMd, config.Cfg.OutputSchema = saved, savedSchema }) | |
| 19 | + dir := t.TempDir() | |
| 20 | + t.Chdir(dir) | |
| 21 | + if text != "" { | |
| 22 | + if err := os.WriteFile("AGENTS.md", []byte(text), 0o644); err != nil { | |
| 23 | + t.Fatal(err) | |
| 24 | + } | |
| 25 | + } | |
| 26 | + abs, _ := filepath.Abs(filepath.Join(dir, "AGENTS.md")) | |
| 27 | + return abs | |
| 28 | +} | |
| 29 | + | |
| 30 | +var agentsMdOn = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | |
| 31 | + | |
| 32 | +func TestBannerNamesTheLoadedFile(t *testing.T) { | |
| 33 | + path := inProject(t, strings.Repeat("a", 700), agentsMdOn) | |
| 34 | + col, warnings := agentsMdBanner(false, 100000) | |
| 35 | + if col != " | agents.md: "+path+" (~200 tokens)" || len(warnings) != 0 { | |
| 36 | + t.Errorf("column %q, warnings %v", col, warnings) | |
| 37 | + } | |
| 38 | +} | |
| 39 | + | |
| 40 | +func TestBannerWarnsWhenTheFileFillsTheWindow(t *testing.T) { | |
| 41 | + path := inProject(t, strings.Repeat("a", 700), agentsMdOn) // ~200 tokens | |
| 42 | + _, warnings := agentsMdBanner(false, 1000) // 20 % | |
| 43 | + if len(warnings) != 1 || !strings.Contains(warnings[0], path+" is ~200 tokens, 20 % of the 1000-token context window") { | |
| 44 | + t.Errorf("warnings %v", warnings) | |
| 45 | + } | |
| 46 | + if _, warnings := agentsMdBanner(false, 1001); len(warnings) != 0 { | |
| 47 | + t.Errorf("under 20 %%: %v", warnings) | |
| 48 | + } | |
| 49 | + if _, warnings := agentsMdBanner(false, 0); len(warnings) != 0 { | |
| 50 | + t.Errorf("unknown window: %v", warnings) | |
| 51 | + } | |
| 52 | +} | |
| 53 | + | |
| 54 | +func TestBannerSaysASkippedFileIsNotRead(t *testing.T) { | |
| 55 | + inProject(t, "too long rules", config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md", MaxChars: 5}) | |
| 56 | + col, warnings := agentsMdBanner(false, 0) | |
| 57 | + if col != " | agents.md: skipped (too long)" || len(warnings) != 1 || !strings.Contains(warnings[0], "14 characters > agentsMd.maxChars 5") { | |
| 58 | + t.Errorf("column %q, warnings %v", col, warnings) | |
| 59 | + } | |
| 60 | +} | |
| 61 | + | |
| 62 | +// A structured agent: a neutral mention, no warning — and only when a file | |
| 63 | +// exists. | |
| 64 | +func TestBannerMentionsAFileAStructuredAgentIgnores(t *testing.T) { | |
| 65 | + inProject(t, "prose rules", agentsMdOn) | |
| 66 | + config.Cfg.OutputSchema = map[string]any{"type": "object"} | |
| 67 | + col, warnings := agentsMdBanner(false, 0) | |
| 68 | + if col != " | agents.md: ignored (structured output)" || len(warnings) != 0 { | |
| 69 | + t.Errorf("column %q, warnings %v", col, warnings) | |
| 70 | + } | |
| 71 | +} | |
| 72 | + | |
| 73 | +func TestBannerSaysNothingWithoutAFileOrWhenOff(t *testing.T) { | |
| 74 | + inProject(t, "", agentsMdOn) | |
| 75 | + if col, warnings := agentsMdBanner(false, 0); col != "" || warnings != nil { | |
| 76 | + t.Errorf("no file: column %q, warnings %v", col, warnings) | |
| 77 | + } | |
| 78 | + inProject(t, "rules", config.AgentsMdConfig{Enabled: false, Path: "AGENTS.md"}) | |
| 79 | + if col, warnings := agentsMdBanner(false, 0); col != "" || warnings != nil { | |
| 80 | + t.Errorf("disabled: column %q, warnings %v", col, warnings) | |
| 81 | + } | |
| 82 | +} | |
| 83 | + | |
| 84 | +func TestBannerInACPModeSaysWhereEachSessionLooks(t *testing.T) { | |
| 85 | + inProject(t, "", agentsMdOn) | |
| 86 | + if col, _ := agentsMdBanner(true, 0); col != " | agents.md: <session cwd>/AGENTS.md" { | |
| 87 | + t.Errorf("column %q", col) | |
| 88 | + } | |
| 89 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,89 @@ | |||
| 1 | +package main | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "os" | ||
| 5 | + "path/filepath" | ||
| 6 | + "strings" | ||
| 7 | + "testing" | ||
| 8 | + | ||
| 9 | + "mm/internal/config" | ||
| 10 | +) | ||
| 11 | + | ||
| 12 | +// inProject runs the test in a fresh directory holding an AGENTS.md of text | ||
| 13 | +// ("" = no file), with the given settings. | ||
| 14 | +func inProject(t *testing.T, text string, settings config.AgentsMdConfig) string { | ||
| 15 | + t.Helper() | ||
| 16 | + saved, savedSchema := config.Cfg.AgentsMd, config.Cfg.OutputSchema | ||
| 17 | + config.Cfg.AgentsMd, config.Cfg.OutputSchema = settings, nil | ||
| 18 | + t.Cleanup(func() { config.Cfg.AgentsMd, config.Cfg.OutputSchema = saved, savedSchema }) | ||
| 19 | + dir := t.TempDir() | ||
| 20 | + t.Chdir(dir) | ||
| 21 | + if text != "" { | ||
| 22 | + if err := os.WriteFile("AGENTS.md", []byte(text), 0o644); err != nil { | ||
| 23 | + t.Fatal(err) | ||
| 24 | + } | ||
| 25 | + } | ||
| 26 | + abs, _ := filepath.Abs(filepath.Join(dir, "AGENTS.md")) | ||
| 27 | + return abs | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +var agentsMdOn = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | ||
| 31 | + | ||
| 32 | +func TestBannerNamesTheLoadedFile(t *testing.T) { | ||
| 33 | + path := inProject(t, strings.Repeat("a", 700), agentsMdOn) | ||
| 34 | + col, warnings := agentsMdBanner(false, 100000) | ||
| 35 | + if col != " | agents.md: "+path+" (~200 tokens)" || len(warnings) != 0 { | ||
| 36 | + t.Errorf("column %q, warnings %v", col, warnings) | ||
| 37 | + } | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +func TestBannerWarnsWhenTheFileFillsTheWindow(t *testing.T) { | ||
| 41 | + path := inProject(t, strings.Repeat("a", 700), agentsMdOn) // ~200 tokens | ||
| 42 | + _, warnings := agentsMdBanner(false, 1000) // 20 % | ||
| 43 | + if len(warnings) != 1 || !strings.Contains(warnings[0], path+" is ~200 tokens, 20 % of the 1000-token context window") { | ||
| 44 | + t.Errorf("warnings %v", warnings) | ||
| 45 | + } | ||
| 46 | + if _, warnings := agentsMdBanner(false, 1001); len(warnings) != 0 { | ||
| 47 | + t.Errorf("under 20 %%: %v", warnings) | ||
| 48 | + } | ||
| 49 | + if _, warnings := agentsMdBanner(false, 0); len(warnings) != 0 { | ||
| 50 | + t.Errorf("unknown window: %v", warnings) | ||
| 51 | + } | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +func TestBannerSaysASkippedFileIsNotRead(t *testing.T) { | ||
| 55 | + inProject(t, "too long rules", config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md", MaxChars: 5}) | ||
| 56 | + col, warnings := agentsMdBanner(false, 0) | ||
| 57 | + if col != " | agents.md: skipped (too long)" || len(warnings) != 1 || !strings.Contains(warnings[0], "14 characters > agentsMd.maxChars 5") { | ||
| 58 | + t.Errorf("column %q, warnings %v", col, warnings) | ||
| 59 | + } | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +// A structured agent: a neutral mention, no warning — and only when a file | ||
| 63 | +// exists. | ||
| 64 | +func TestBannerMentionsAFileAStructuredAgentIgnores(t *testing.T) { | ||
| 65 | + inProject(t, "prose rules", agentsMdOn) | ||
| 66 | + config.Cfg.OutputSchema = map[string]any{"type": "object"} | ||
| 67 | + col, warnings := agentsMdBanner(false, 0) | ||
| 68 | + if col != " | agents.md: ignored (structured output)" || len(warnings) != 0 { | ||
| 69 | + t.Errorf("column %q, warnings %v", col, warnings) | ||
| 70 | + } | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +func TestBannerSaysNothingWithoutAFileOrWhenOff(t *testing.T) { | ||
| 74 | + inProject(t, "", agentsMdOn) | ||
| 75 | + if col, warnings := agentsMdBanner(false, 0); col != "" || warnings != nil { | ||
| 76 | + t.Errorf("no file: column %q, warnings %v", col, warnings) | ||
| 77 | + } | ||
| 78 | + inProject(t, "rules", config.AgentsMdConfig{Enabled: false, Path: "AGENTS.md"}) | ||
| 79 | + if col, warnings := agentsMdBanner(false, 0); col != "" || warnings != nil { | ||
| 80 | + t.Errorf("disabled: column %q, warnings %v", col, warnings) | ||
| 81 | + } | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +func TestBannerInACPModeSaysWhereEachSessionLooks(t *testing.T) { | ||
| 85 | + inProject(t, "", agentsMdOn) | ||
| 86 | + if col, _ := agentsMdBanner(true, 0); col != " | agents.md: <session cwd>/AGENTS.md" { | ||
| 87 | + t.Errorf("column %q", col) | ||
| 88 | + } | ||
| 89 | +} | ||
added
demo-agents-file/README.en.md +159 -0 | new file mode 100644 | ||
| @@ -0,0 +1,159 @@ | ||
| 1 | +# mini-me demo: the project's instructions (`AGENTS.md`) | |
| 2 | + | |
| 3 | +This directory shows how mini-me reads the `AGENTS.md` file of the project it works in and adds it to its system prompt: the agent then follows the project's rules (which command runs the tests, what not to touch…) without any change to its configuration. The models are served by [llmman](https://github.com/llmmanorg/llmman). Version française : [README.md](README.md). | |
| 4 | + | |
| 5 | +## What is here | |
| 6 | + | |
| 7 | +| File | Role | | |
| 8 | +|---|---| | |
| 9 | +| `project/` | A small "project", **Tangerine**: the directory mm is started in. | | |
| 10 | +| `project/AGENTS.md` | The project's instructions: tests run with `make check` (never `go test`), do not touch `vendor/` or `generated/`, sign every answer `-- Tangerine bot`. | | |
| 11 | +| `project/Makefile`, `project/greet.sh` | What `make check` runs. | | |
| 12 | +| `agent.yaml` | **Bob**, a coding agent (`bash`, file tools, `ask_agent`). He reads `AGENTS.md` (the `agentsMd` setting is written out in full, with its default values). | | |
| 13 | +| `agent.helper.yaml` | **helper**, asked by Bob: no tools, **reads** `AGENTS.md` (the default). | | |
| 14 | +| `agent.reviewer.yaml` | **reviewer**, asked by Bob: no tools, does **not** read `AGENTS.md` (`agentsMd.enabled: false`). | | |
| 15 | + | |
| 16 | +All three agents use `hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`. | |
| 17 | + | |
| 18 | +## Prerequisites | |
| 19 | + | |
| 20 | +1. llmman running (`llmman serve`, on `127.0.0.1:17434`; from a Docker Sandbox, mm falls back to `host.docker.internal:17434` by itself), and the model pulled: `llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`. | |
| 21 | +2. `mm` built at the repository root (`go build -o mm .`). | |
| 22 | +3. **Start mm from `project/`**: `AGENTS.md` is looked for in the working directory, not next to the configuration file. | |
| 23 | + | |
| 24 | + ```bash | |
| 25 | + cd demo-agents-file/project | |
| 26 | + ``` | |
| 27 | + | |
| 28 | +## The setting | |
| 29 | + | |
| 30 | +In `agent.yaml` (these are the defaults: a config that never mentions `agentsMd` reads `./AGENTS.md` all the same): | |
| 31 | + | |
| 32 | +```yaml | |
| 33 | +agentsMd: | |
| 34 | + enabled: true # read at the start of every conversation: start-up, /new, /resume | |
| 35 | + path: AGENTS.md # relative to the working directory (the project), not to this file | |
| 36 | + maxChars: 0 # 0 = no limit; above it the file is skipped whole, never cut | |
| 37 | +``` | |
| 38 | + | |
| 39 | +The model receives its `system` prompt, then: | |
| 40 | + | |
| 41 | +```text | |
| 42 | +PROJECT INSTRUCTIONS (from /…/demo-agents-file/project/AGENTS.md) | |
| 43 | +The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins. | |
| 44 | + | |
| 45 | +# Project "Tangerine" — instructions for coding agents | |
| 46 | +… | |
| 47 | +``` | |
| 48 | + | |
| 49 | +## 1. The banner says which file is read | |
| 50 | + | |
| 51 | +```bash | |
| 52 | +../../mm ../agent.yaml | |
| 53 | +``` | |
| 54 | + | |
| 55 | +``` | |
| 56 | +mm … | tools: bash, read_file, write_file, edit_file, ask_agent | skills: 0 | yolo: on | agents.md: /…/demo-agents-file/project/AGENTS.md (~125 tokens) | |
| 57 | +``` | |
| 58 | + | |
| 59 | +(The `no skills found` warning is expected: this demo has no skills.) | |
| 60 | + | |
| 61 | +## 2. Bob follows the project's instructions | |
| 62 | + | |
| 63 | +```bash | |
| 64 | +../../mm ../agent.yaml -p "Run the tests." | |
| 65 | +``` | |
| 66 | + | |
| 67 | +``` | |
| 68 | +🛠️ bash: make check | |
| 69 | + │ running the Tangerine checks… | |
| 70 | + │ ✅ all checks passed | |
| 71 | +🤖 ✅ all checks passed | |
| 72 | +``` | |
| 73 | + | |
| 74 | +Bob runs `make check`, the command `AGENTS.md` sets, not a guessed one. | |
| 75 | + | |
| 76 | +**To compare**, move the file aside and ask the same thing. `AGENT_YOLO=false` keeps the commands from running: you see what Bob *wanted* to run. | |
| 77 | + | |
| 78 | +```bash | |
| 79 | +mv AGENTS.md AGENTS.md.off | |
| 80 | +AGENT_YOLO=false ../../mm ../agent.yaml -p "Run the tests." | |
| 81 | +mv AGENTS.md.off AGENTS.md | |
| 82 | +``` | |
| 83 | + | |
| 84 | +``` | |
| 85 | +🛠️ bash: python -m pytest | |
| 86 | + │ [command rejected by the user] | |
| 87 | +🛠️ bash: pytest | |
| 88 | + │ [command rejected by the user] | |
| 89 | +… | |
| 90 | +``` | |
| 91 | + | |
| 92 | +Without instructions the model guesses (here `pytest`, in a project without a line of Python; in a run with `yolo: true` it went as far as `pip3 install pytest`). | |
| 93 | + | |
| 94 | +## 3. An asked agent decides with ITS OWN config | |
| 95 | + | |
| 96 | +When Bob hands a question to another agent (`ask_agent`, `/ask`, `mm --ask`), the asked agent works in the same directory, but **its own** `agentsMd` setting decides whether it reads the file. | |
| 97 | + | |
| 98 | +```bash | |
| 99 | +../../mm ../agent.yaml --ask helper -p "How do I run the tests in this project?" | |
| 100 | +``` | |
| 101 | + | |
| 102 | +``` | |
| 103 | +To run the tests, execute `make check` as instructed by the project's build and test guidelines. Always quote the last line of the command output in your response. | |
| 104 | +-- Tangerine bot | |
| 105 | +``` | |
| 106 | + | |
| 107 | +```bash | |
| 108 | +../../mm ../agent.yaml --ask reviewer -p "How do I run the tests in this project?" | |
| 109 | +``` | |
| 110 | + | |
| 111 | +``` | |
| 112 | +Run the tests by executing the command specified in your project's documentation, typically `npm test` for Node.js projects or `pytest` for Python projects. … | |
| 113 | +``` | |
| 114 | + | |
| 115 | +helper knows `make check` and signs; reviewer (`agentsMd.enabled: false`) gives a generic answer: it never saw the file. | |
| 116 | + | |
| 117 | +## 4. Editing `AGENTS.md` during a session | |
| 118 | + | |
| 119 | +The file is read **at the start of every conversation**, never in the middle of one (changing the system message mid-way would make the server re-read the whole history). To take an edit into account: | |
| 120 | + | |
| 121 | +```bash | |
| 122 | +../../mm ../agent.yaml | |
| 123 | +> Run the tests. # make check | |
| 124 | +``` | |
| 125 | + | |
| 126 | +Edit `AGENTS.md` in another terminal (for instance: the command becomes `sh greet.sh Tangerine`), then: | |
| 127 | + | |
| 128 | +```text | |
| 129 | +> /new | |
| 130 | +🆕 new session: 4 message(s) forgotten | |
| 131 | +> Run the tests. | |
| 132 | +🛠️ bash: sh greet.sh Tangerine | |
| 133 | +``` | |
| 134 | + | |
| 135 | +The new instruction applies from the new conversation on. (In our run Bob then also ran `make check`, despite "Never run make": a 4B model does not honour every prohibition.) | |
| 136 | + | |
| 137 | +`/resume` also reads the current file. | |
| 138 | + | |
| 139 | +## 5. Check what the model received | |
| 140 | + | |
| 141 | +```bash | |
| 142 | +AGENT_TRACE=true ../../mm ../agent.yaml -p "Run the tests." | |
| 143 | +jq -r '.messages[0].content' .mm/traces/*-0001.request.json | |
| 144 | +``` | |
| 145 | + | |
| 146 | +The system message ends with the `PROJECT INSTRUCTIONS (from …)` section. | |
| 147 | + | |
| 148 | +## Good to know | |
| 149 | + | |
| 150 | +- **A small model does not follow every rule every time**: with Qwen3.5-4B, Bob ran `make check` on every try but left out the `-- Tangerine bot` signature; helper, with no tools, added it. Short, concrete instructions work best. | |
| 151 | +- **Size**: the file rides with every request. The banner warns when it takes 20 % or more of the context window; `agentsMd.maxChars` sets a hard limit (above it the file is skipped whole, and the banner says `skipped (too long)`). | |
| 152 | +- **A JSON agent** (`structuredOutput`) never reads `AGENTS.md`; its banner says `agents.md: ignored (structured output)`. See [demo-structured-output](../demo-structured-output/README.en.md). | |
| 153 | +- **In an editor** (Zed…, ACP mode), each session reads the `AGENTS.md` of the project it opened. | |
| 154 | + | |
| 155 | +## Further reading | |
| 156 | + | |
| 157 | +- [Give the agent your project's instructions](../docs/en/how-to/give-project-instructions.md) | |
| 158 | +- [`agentsMd` reference](../docs/en/reference/configuration.md#agentsmd-keys) | |
| 159 | +- [Project instructions — explanation](../docs/en/explanation/project-instructions.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,159 @@ | |||
| 1 | +# mini-me demo: the project's instructions (`AGENTS.md`) | ||
| 2 | + | ||
| 3 | +This directory shows how mini-me reads the `AGENTS.md` file of the project it works in and adds it to its system prompt: the agent then follows the project's rules (which command runs the tests, what not to touch…) without any change to its configuration. The models are served by [llmman](https://github.com/llmmanorg/llmman). Version française : [README.md](README.md). | ||
| 4 | + | ||
| 5 | +## What is here | ||
| 6 | + | ||
| 7 | +| File | Role | | ||
| 8 | +|---|---| | ||
| 9 | +| `project/` | A small "project", **Tangerine**: the directory mm is started in. | | ||
| 10 | +| `project/AGENTS.md` | The project's instructions: tests run with `make check` (never `go test`), do not touch `vendor/` or `generated/`, sign every answer `-- Tangerine bot`. | | ||
| 11 | +| `project/Makefile`, `project/greet.sh` | What `make check` runs. | | ||
| 12 | +| `agent.yaml` | **Bob**, a coding agent (`bash`, file tools, `ask_agent`). He reads `AGENTS.md` (the `agentsMd` setting is written out in full, with its default values). | | ||
| 13 | +| `agent.helper.yaml` | **helper**, asked by Bob: no tools, **reads** `AGENTS.md` (the default). | | ||
| 14 | +| `agent.reviewer.yaml` | **reviewer**, asked by Bob: no tools, does **not** read `AGENTS.md` (`agentsMd.enabled: false`). | | ||
| 15 | + | ||
| 16 | +All three agents use `hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`. | ||
| 17 | + | ||
| 18 | +## Prerequisites | ||
| 19 | + | ||
| 20 | +1. llmman running (`llmman serve`, on `127.0.0.1:17434`; from a Docker Sandbox, mm falls back to `host.docker.internal:17434` by itself), and the model pulled: `llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`. | ||
| 21 | +2. `mm` built at the repository root (`go build -o mm .`). | ||
| 22 | +3. **Start mm from `project/`**: `AGENTS.md` is looked for in the working directory, not next to the configuration file. | ||
| 23 | + | ||
| 24 | + ```bash | ||
| 25 | + cd demo-agents-file/project | ||
| 26 | + ``` | ||
| 27 | + | ||
| 28 | +## The setting | ||
| 29 | + | ||
| 30 | +In `agent.yaml` (these are the defaults: a config that never mentions `agentsMd` reads `./AGENTS.md` all the same): | ||
| 31 | + | ||
| 32 | +```yaml | ||
| 33 | +agentsMd: | ||
| 34 | + enabled: true # read at the start of every conversation: start-up, /new, /resume | ||
| 35 | + path: AGENTS.md # relative to the working directory (the project), not to this file | ||
| 36 | + maxChars: 0 # 0 = no limit; above it the file is skipped whole, never cut | ||
| 37 | +``` | ||
| 38 | + | ||
| 39 | +The model receives its `system` prompt, then: | ||
| 40 | + | ||
| 41 | +```text | ||
| 42 | +PROJECT INSTRUCTIONS (from /…/demo-agents-file/project/AGENTS.md) | ||
| 43 | +The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins. | ||
| 44 | + | ||
| 45 | +# Project "Tangerine" — instructions for coding agents | ||
| 46 | +… | ||
| 47 | +``` | ||
| 48 | + | ||
| 49 | +## 1. The banner says which file is read | ||
| 50 | + | ||
| 51 | +```bash | ||
| 52 | +../../mm ../agent.yaml | ||
| 53 | +``` | ||
| 54 | + | ||
| 55 | +``` | ||
| 56 | +mm … | tools: bash, read_file, write_file, edit_file, ask_agent | skills: 0 | yolo: on | agents.md: /…/demo-agents-file/project/AGENTS.md (~125 tokens) | ||
| 57 | +``` | ||
| 58 | + | ||
| 59 | +(The `no skills found` warning is expected: this demo has no skills.) | ||
| 60 | + | ||
| 61 | +## 2. Bob follows the project's instructions | ||
| 62 | + | ||
| 63 | +```bash | ||
| 64 | +../../mm ../agent.yaml -p "Run the tests." | ||
| 65 | +``` | ||
| 66 | + | ||
| 67 | +``` | ||
| 68 | +🛠️ bash: make check | ||
| 69 | + │ running the Tangerine checks… | ||
| 70 | + │ ✅ all checks passed | ||
| 71 | +🤖 ✅ all checks passed | ||
| 72 | +``` | ||
| 73 | + | ||
| 74 | +Bob runs `make check`, the command `AGENTS.md` sets, not a guessed one. | ||
| 75 | + | ||
| 76 | +**To compare**, move the file aside and ask the same thing. `AGENT_YOLO=false` keeps the commands from running: you see what Bob *wanted* to run. | ||
| 77 | + | ||
| 78 | +```bash | ||
| 79 | +mv AGENTS.md AGENTS.md.off | ||
| 80 | +AGENT_YOLO=false ../../mm ../agent.yaml -p "Run the tests." | ||
| 81 | +mv AGENTS.md.off AGENTS.md | ||
| 82 | +``` | ||
| 83 | + | ||
| 84 | +``` | ||
| 85 | +🛠️ bash: python -m pytest | ||
| 86 | + │ [command rejected by the user] | ||
| 87 | +🛠️ bash: pytest | ||
| 88 | + │ [command rejected by the user] | ||
| 89 | +… | ||
| 90 | +``` | ||
| 91 | + | ||
| 92 | +Without instructions the model guesses (here `pytest`, in a project without a line of Python; in a run with `yolo: true` it went as far as `pip3 install pytest`). | ||
| 93 | + | ||
| 94 | +## 3. An asked agent decides with ITS OWN config | ||
| 95 | + | ||
| 96 | +When Bob hands a question to another agent (`ask_agent`, `/ask`, `mm --ask`), the asked agent works in the same directory, but **its own** `agentsMd` setting decides whether it reads the file. | ||
| 97 | + | ||
| 98 | +```bash | ||
| 99 | +../../mm ../agent.yaml --ask helper -p "How do I run the tests in this project?" | ||
| 100 | +``` | ||
| 101 | + | ||
| 102 | +``` | ||
| 103 | +To run the tests, execute `make check` as instructed by the project's build and test guidelines. Always quote the last line of the command output in your response. | ||
| 104 | +-- Tangerine bot | ||
| 105 | +``` | ||
| 106 | + | ||
| 107 | +```bash | ||
| 108 | +../../mm ../agent.yaml --ask reviewer -p "How do I run the tests in this project?" | ||
| 109 | +``` | ||
| 110 | + | ||
| 111 | +``` | ||
| 112 | +Run the tests by executing the command specified in your project's documentation, typically `npm test` for Node.js projects or `pytest` for Python projects. … | ||
| 113 | +``` | ||
| 114 | + | ||
| 115 | +helper knows `make check` and signs; reviewer (`agentsMd.enabled: false`) gives a generic answer: it never saw the file. | ||
| 116 | + | ||
| 117 | +## 4. Editing `AGENTS.md` during a session | ||
| 118 | + | ||
| 119 | +The file is read **at the start of every conversation**, never in the middle of one (changing the system message mid-way would make the server re-read the whole history). To take an edit into account: | ||
| 120 | + | ||
| 121 | +```bash | ||
| 122 | +../../mm ../agent.yaml | ||
| 123 | +> Run the tests. # make check | ||
| 124 | +``` | ||
| 125 | + | ||
| 126 | +Edit `AGENTS.md` in another terminal (for instance: the command becomes `sh greet.sh Tangerine`), then: | ||
| 127 | + | ||
| 128 | +```text | ||
| 129 | +> /new | ||
| 130 | +🆕 new session: 4 message(s) forgotten | ||
| 131 | +> Run the tests. | ||
| 132 | +🛠️ bash: sh greet.sh Tangerine | ||
| 133 | +``` | ||
| 134 | + | ||
| 135 | +The new instruction applies from the new conversation on. (In our run Bob then also ran `make check`, despite "Never run make": a 4B model does not honour every prohibition.) | ||
| 136 | + | ||
| 137 | +`/resume` also reads the current file. | ||
| 138 | + | ||
| 139 | +## 5. Check what the model received | ||
| 140 | + | ||
| 141 | +```bash | ||
| 142 | +AGENT_TRACE=true ../../mm ../agent.yaml -p "Run the tests." | ||
| 143 | +jq -r '.messages[0].content' .mm/traces/*-0001.request.json | ||
| 144 | +``` | ||
| 145 | + | ||
| 146 | +The system message ends with the `PROJECT INSTRUCTIONS (from …)` section. | ||
| 147 | + | ||
| 148 | +## Good to know | ||
| 149 | + | ||
| 150 | +- **A small model does not follow every rule every time**: with Qwen3.5-4B, Bob ran `make check` on every try but left out the `-- Tangerine bot` signature; helper, with no tools, added it. Short, concrete instructions work best. | ||
| 151 | +- **Size**: the file rides with every request. The banner warns when it takes 20 % or more of the context window; `agentsMd.maxChars` sets a hard limit (above it the file is skipped whole, and the banner says `skipped (too long)`). | ||
| 152 | +- **A JSON agent** (`structuredOutput`) never reads `AGENTS.md`; its banner says `agents.md: ignored (structured output)`. See [demo-structured-output](../demo-structured-output/README.en.md). | ||
| 153 | +- **In an editor** (Zed…, ACP mode), each session reads the `AGENTS.md` of the project it opened. | ||
| 154 | + | ||
| 155 | +## Further reading | ||
| 156 | + | ||
| 157 | +- [Give the agent your project's instructions](../docs/en/how-to/give-project-instructions.md) | ||
| 158 | +- [`agentsMd` reference](../docs/en/reference/configuration.md#agentsmd-keys) | ||
| 159 | +- [Project instructions — explanation](../docs/en/explanation/project-instructions.md) | ||
added
demo-agents-file/README.md +159 -0 | new file mode 100644 | ||
| @@ -0,0 +1,159 @@ | ||
| 1 | +# Démo mini-me : les consignes du projet (`AGENTS.md`) | |
| 2 | + | |
| 3 | +Ce dossier montre comment mini-me lit le fichier `AGENTS.md` du projet dans lequel il travaille et l'ajoute à son prompt système : l'agent suit alors les règles du projet (quelle commande lance les tests, ce qu'il ne faut pas toucher…) sans qu'on modifie sa configuration. Les modèles sont servis par [llmman](https://github.com/llmmanorg/llmman). English version: [README.en.md](README.en.md). | |
| 4 | + | |
| 5 | +## Ce qu'il y a ici | |
| 6 | + | |
| 7 | +| Fichier | Rôle | | |
| 8 | +|---|---| | |
| 9 | +| `project/` | Un petit « projet », **Tangerine** : c'est le dossier dans lequel on lance mm. | | |
| 10 | +| `project/AGENTS.md` | Les consignes du projet : les tests se lancent avec `make check` (jamais `go test`), ne pas toucher `vendor/` ni `generated/`, signer chaque réponse `-- Tangerine bot`. | | |
| 11 | +| `project/Makefile`, `project/greet.sh` | De quoi faire tourner `make check`. | | |
| 12 | +| `agent.yaml` | **Bob**, un agent de code (`bash`, outils de fichiers, `ask_agent`). Il lit `AGENTS.md` (le réglage `agentsMd` y est écrit en entier, avec ses valeurs par défaut). | | |
| 13 | +| `agent.helper.yaml` | **helper**, appelé par Bob : sans outils, il **lit** `AGENTS.md` (réglage par défaut). | | |
| 14 | +| `agent.reviewer.yaml` | **reviewer**, appelé par Bob : sans outils, il **ne lit pas** `AGENTS.md` (`agentsMd.enabled: false`). | | |
| 15 | + | |
| 16 | +Les trois agents utilisent `hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`. | |
| 17 | + | |
| 18 | +## Prérequis | |
| 19 | + | |
| 20 | +1. llmman qui tourne (`llmman serve`, sur `127.0.0.1:17434` ; depuis un Docker Sandbox, mm se replie tout seul sur `host.docker.internal:17434`), et le modèle téléchargé : `llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`. | |
| 21 | +2. `mm` compilé à la racine du dépôt (`go build -o mm .`). | |
| 22 | +3. **Lancez mm depuis `project/`** : `AGENTS.md` est cherché dans le répertoire de travail, pas à côté du fichier de configuration. | |
| 23 | + | |
| 24 | + ```bash | |
| 25 | + cd demo-agents-file/project | |
| 26 | + ``` | |
| 27 | + | |
| 28 | +## Le réglage | |
| 29 | + | |
| 30 | +Dans `agent.yaml` (ce sont les valeurs par défaut : une config qui ne mentionne pas `agentsMd` lit `./AGENTS.md` de la même façon) : | |
| 31 | + | |
| 32 | +```yaml | |
| 33 | +agentsMd: | |
| 34 | + enabled: true # lu au début de chaque conversation : démarrage, /new, /resume | |
| 35 | + path: AGENTS.md # relatif au répertoire de travail (le projet), pas à ce fichier | |
| 36 | + maxChars: 0 # 0 = pas de limite ; au-delà, le fichier est ignoré en entier, jamais coupé | |
| 37 | +``` | |
| 38 | + | |
| 39 | +Le modèle reçoit son prompt `system`, puis : | |
| 40 | + | |
| 41 | +```text | |
| 42 | +PROJECT INSTRUCTIONS (from /…/demo-agents-file/project/AGENTS.md) | |
| 43 | +The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins. | |
| 44 | + | |
| 45 | +# Project "Tangerine" — instructions for coding agents | |
| 46 | +… | |
| 47 | +``` | |
| 48 | + | |
| 49 | +## 1. La bannière dit quel fichier est lu | |
| 50 | + | |
| 51 | +```bash | |
| 52 | +mm ../agent.yaml | |
| 53 | +``` | |
| 54 | + | |
| 55 | +``` | |
| 56 | +mm … | tools: bash, read_file, write_file, edit_file, ask_agent | skills: 0 | yolo: on | agents.md: /…/demo-agents-file/project/AGENTS.md (~125 tokens) | |
| 57 | +``` | |
| 58 | + | |
| 59 | +(L'avertissement `no skills found` est normal : cette démo n'a pas de skills.) | |
| 60 | + | |
| 61 | +## 2. Bob suit les consignes du projet | |
| 62 | + | |
| 63 | +```bash | |
| 64 | +mm ../agent.yaml -p "Run the tests." | |
| 65 | +``` | |
| 66 | + | |
| 67 | +``` | |
| 68 | +🛠️ bash: make check | |
| 69 | + │ running the Tangerine checks… | |
| 70 | + │ ✅ all checks passed | |
| 71 | +🤖 ✅ all checks passed | |
| 72 | +``` | |
| 73 | + | |
| 74 | +Bob lance `make check`, la commande que fixe `AGENTS.md`, et pas une commande devinée. | |
| 75 | + | |
| 76 | +**Pour comparer**, mettez le fichier de côté et reposez la même question. `AGENT_YOLO=false` empêche les commandes de s'exécuter : on voit ce que Bob *voulait* lancer. | |
| 77 | + | |
| 78 | +```bash | |
| 79 | +mv AGENTS.md AGENTS.md.off | |
| 80 | +AGENT_YOLO=false ../../mm ../agent.yaml -p "Run the tests." | |
| 81 | +mv AGENTS.md.off AGENTS.md | |
| 82 | +``` | |
| 83 | + | |
| 84 | +``` | |
| 85 | +🛠️ bash: python -m pytest | |
| 86 | + │ [command rejected by the user] | |
| 87 | +🛠️ bash: pytest | |
| 88 | + │ [command rejected by the user] | |
| 89 | +… | |
| 90 | +``` | |
| 91 | + | |
| 92 | +Sans consigne, le modèle devine (ici, `pytest`, alors que le projet n'a pas une ligne de Python ; lors d'un essai avec `yolo: true`, il est allé jusqu'à lancer `pip3 install pytest`). | |
| 93 | + | |
| 94 | +## 3. Un agent appelé décide avec SA propre config | |
| 95 | + | |
| 96 | +Quand Bob confie une question à un autre agent (`ask_agent`, `/ask`, `mm --ask`), l'agent appelé travaille dans le même répertoire, mais c'est **son** réglage `agentsMd` qui décide s'il lit le fichier. | |
| 97 | + | |
| 98 | +```bash | |
| 99 | +mm ../agent.yaml --ask helper -p "How do I run the tests in this project?" | |
| 100 | +``` | |
| 101 | + | |
| 102 | +``` | |
| 103 | +To run the tests, execute `make check` as instructed by the project's build and test guidelines. Always quote the last line of the command output in your response. | |
| 104 | +-- Tangerine bot | |
| 105 | +``` | |
| 106 | + | |
| 107 | +```bash | |
| 108 | +mm ../agent.yaml --ask reviewer -p "How do I run the tests in this project?" | |
| 109 | +``` | |
| 110 | + | |
| 111 | +``` | |
| 112 | +Run the tests by executing the command specified in your project's documentation, typically `npm test` for Node.js projects or `pytest` for Python projects. … | |
| 113 | +``` | |
| 114 | + | |
| 115 | +helper connaît `make check` et signe ; reviewer (`agentsMd.enabled: false`) donne une réponse générique : il n'a pas vu le fichier. | |
| 116 | + | |
| 117 | +## 4. Modifier `AGENTS.md` pendant une session | |
| 118 | + | |
| 119 | +Le fichier est lu **au début de chaque conversation**, jamais au milieu (changer le message système en cours de route obligerait le serveur à relire tout l'historique). Pour prendre une modification en compte : | |
| 120 | + | |
| 121 | +```bash | |
| 122 | +../../mm ../agent.yaml | |
| 123 | +> Run the tests. # make check | |
| 124 | +``` | |
| 125 | + | |
| 126 | +Modifiez `AGENTS.md` dans un autre terminal (par exemple : la commande devient `sh greet.sh Tangerine`), puis : | |
| 127 | + | |
| 128 | +```text | |
| 129 | +> /new | |
| 130 | +🆕 new session: 4 message(s) forgotten | |
| 131 | +> Run the tests. | |
| 132 | +🛠️ bash: sh greet.sh Tangerine | |
| 133 | +``` | |
| 134 | + | |
| 135 | +La nouvelle consigne s'applique dès la nouvelle conversation. (Lors de notre essai, Bob a ensuite lancé aussi `make check`, malgré « Never run make » : un modèle 4B ne respecte pas chaque interdiction.) | |
| 136 | + | |
| 137 | +`/resume` relit aussi le fichier actuel. | |
| 138 | + | |
| 139 | +## 5. Vérifier ce que le modèle a reçu | |
| 140 | + | |
| 141 | +```bash | |
| 142 | +AGENT_TRACE=true ../../mm ../agent.yaml -p "Run the tests." | |
| 143 | +jq -r '.messages[0].content' .mm/traces/*-0001.request.json | |
| 144 | +``` | |
| 145 | + | |
| 146 | +Le message système se termine par la section `PROJECT INSTRUCTIONS (from …)`. | |
| 147 | + | |
| 148 | +## Ce qu'il faut savoir | |
| 149 | + | |
| 150 | +- **Un petit modèle ne suit pas toutes les règles à chaque fois** : avec Qwen3.5-4B, Bob a lancé `make check` à chaque essai, mais a oublié la signature `-- Tangerine bot` ; helper, sans outils, l'a mise. Des consignes courtes et concrètes passent mieux. | |
| 151 | +- **Taille** : le fichier part avec chaque requête. La bannière avertit quand il occupe 20 % ou plus de la fenêtre de contexte ; `agentsMd.maxChars` fixe une limite ferme (au-delà, il est ignoré en entier, et la bannière l'indique `skipped (too long)`). | |
| 152 | +- **Un agent JSON** (`structuredOutput`) ne lit jamais `AGENTS.md` ; sa bannière indique `agents.md: ignored (structured output)`. Voir [demo-structured-output](../demo-structured-output/README.md). | |
| 153 | +- **Dans un éditeur** (Zed…, mode ACP), chaque session lit le `AGENTS.md` du projet ouvert. | |
| 154 | + | |
| 155 | +## Pour aller plus loin | |
| 156 | + | |
| 157 | +- [Donner à l'agent les consignes de votre projet](../docs/fr/how-to/give-project-instructions.md) | |
| 158 | +- [Référence de `agentsMd`](../docs/fr/reference/configuration.md#clés-de-agentsmd) | |
| 159 | +- [Consignes du projet — explication](../docs/fr/explanation/project-instructions.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,159 @@ | |||
| 1 | +# Démo mini-me : les consignes du projet (`AGENTS.md`) | ||
| 2 | + | ||
| 3 | +Ce dossier montre comment mini-me lit le fichier `AGENTS.md` du projet dans lequel il travaille et l'ajoute à son prompt système : l'agent suit alors les règles du projet (quelle commande lance les tests, ce qu'il ne faut pas toucher…) sans qu'on modifie sa configuration. Les modèles sont servis par [llmman](https://github.com/llmmanorg/llmman). English version: [README.en.md](README.en.md). | ||
| 4 | + | ||
| 5 | +## Ce qu'il y a ici | ||
| 6 | + | ||
| 7 | +| Fichier | Rôle | | ||
| 8 | +|---|---| | ||
| 9 | +| `project/` | Un petit « projet », **Tangerine** : c'est le dossier dans lequel on lance mm. | | ||
| 10 | +| `project/AGENTS.md` | Les consignes du projet : les tests se lancent avec `make check` (jamais `go test`), ne pas toucher `vendor/` ni `generated/`, signer chaque réponse `-- Tangerine bot`. | | ||
| 11 | +| `project/Makefile`, `project/greet.sh` | De quoi faire tourner `make check`. | | ||
| 12 | +| `agent.yaml` | **Bob**, un agent de code (`bash`, outils de fichiers, `ask_agent`). Il lit `AGENTS.md` (le réglage `agentsMd` y est écrit en entier, avec ses valeurs par défaut). | | ||
| 13 | +| `agent.helper.yaml` | **helper**, appelé par Bob : sans outils, il **lit** `AGENTS.md` (réglage par défaut). | | ||
| 14 | +| `agent.reviewer.yaml` | **reviewer**, appelé par Bob : sans outils, il **ne lit pas** `AGENTS.md` (`agentsMd.enabled: false`). | | ||
| 15 | + | ||
| 16 | +Les trois agents utilisent `hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`. | ||
| 17 | + | ||
| 18 | +## Prérequis | ||
| 19 | + | ||
| 20 | +1. llmman qui tourne (`llmman serve`, sur `127.0.0.1:17434` ; depuis un Docker Sandbox, mm se replie tout seul sur `host.docker.internal:17434`), et le modèle téléchargé : `llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M`. | ||
| 21 | +2. `mm` compilé à la racine du dépôt (`go build -o mm .`). | ||
| 22 | +3. **Lancez mm depuis `project/`** : `AGENTS.md` est cherché dans le répertoire de travail, pas à côté du fichier de configuration. | ||
| 23 | + | ||
| 24 | + ```bash | ||
| 25 | + cd demo-agents-file/project | ||
| 26 | + ``` | ||
| 27 | + | ||
| 28 | +## Le réglage | ||
| 29 | + | ||
| 30 | +Dans `agent.yaml` (ce sont les valeurs par défaut : une config qui ne mentionne pas `agentsMd` lit `./AGENTS.md` de la même façon) : | ||
| 31 | + | ||
| 32 | +```yaml | ||
| 33 | +agentsMd: | ||
| 34 | + enabled: true # lu au début de chaque conversation : démarrage, /new, /resume | ||
| 35 | + path: AGENTS.md # relatif au répertoire de travail (le projet), pas à ce fichier | ||
| 36 | + maxChars: 0 # 0 = pas de limite ; au-delà, le fichier est ignoré en entier, jamais coupé | ||
| 37 | +``` | ||
| 38 | + | ||
| 39 | +Le modèle reçoit son prompt `system`, puis : | ||
| 40 | + | ||
| 41 | +```text | ||
| 42 | +PROJECT INSTRUCTIONS (from /…/demo-agents-file/project/AGENTS.md) | ||
| 43 | +The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins. | ||
| 44 | + | ||
| 45 | +# Project "Tangerine" — instructions for coding agents | ||
| 46 | +… | ||
| 47 | +``` | ||
| 48 | + | ||
| 49 | +## 1. La bannière dit quel fichier est lu | ||
| 50 | + | ||
| 51 | +```bash | ||
| 52 | +mm ../agent.yaml | ||
| 53 | +``` | ||
| 54 | + | ||
| 55 | +``` | ||
| 56 | +mm … | tools: bash, read_file, write_file, edit_file, ask_agent | skills: 0 | yolo: on | agents.md: /…/demo-agents-file/project/AGENTS.md (~125 tokens) | ||
| 57 | +``` | ||
| 58 | + | ||
| 59 | +(L'avertissement `no skills found` est normal : cette démo n'a pas de skills.) | ||
| 60 | + | ||
| 61 | +## 2. Bob suit les consignes du projet | ||
| 62 | + | ||
| 63 | +```bash | ||
| 64 | +mm ../agent.yaml -p "Run the tests." | ||
| 65 | +``` | ||
| 66 | + | ||
| 67 | +``` | ||
| 68 | +🛠️ bash: make check | ||
| 69 | + │ running the Tangerine checks… | ||
| 70 | + │ ✅ all checks passed | ||
| 71 | +🤖 ✅ all checks passed | ||
| 72 | +``` | ||
| 73 | + | ||
| 74 | +Bob lance `make check`, la commande que fixe `AGENTS.md`, et pas une commande devinée. | ||
| 75 | + | ||
| 76 | +**Pour comparer**, mettez le fichier de côté et reposez la même question. `AGENT_YOLO=false` empêche les commandes de s'exécuter : on voit ce que Bob *voulait* lancer. | ||
| 77 | + | ||
| 78 | +```bash | ||
| 79 | +mv AGENTS.md AGENTS.md.off | ||
| 80 | +AGENT_YOLO=false ../../mm ../agent.yaml -p "Run the tests." | ||
| 81 | +mv AGENTS.md.off AGENTS.md | ||
| 82 | +``` | ||
| 83 | + | ||
| 84 | +``` | ||
| 85 | +🛠️ bash: python -m pytest | ||
| 86 | + │ [command rejected by the user] | ||
| 87 | +🛠️ bash: pytest | ||
| 88 | + │ [command rejected by the user] | ||
| 89 | +… | ||
| 90 | +``` | ||
| 91 | + | ||
| 92 | +Sans consigne, le modèle devine (ici, `pytest`, alors que le projet n'a pas une ligne de Python ; lors d'un essai avec `yolo: true`, il est allé jusqu'à lancer `pip3 install pytest`). | ||
| 93 | + | ||
| 94 | +## 3. Un agent appelé décide avec SA propre config | ||
| 95 | + | ||
| 96 | +Quand Bob confie une question à un autre agent (`ask_agent`, `/ask`, `mm --ask`), l'agent appelé travaille dans le même répertoire, mais c'est **son** réglage `agentsMd` qui décide s'il lit le fichier. | ||
| 97 | + | ||
| 98 | +```bash | ||
| 99 | +mm ../agent.yaml --ask helper -p "How do I run the tests in this project?" | ||
| 100 | +``` | ||
| 101 | + | ||
| 102 | +``` | ||
| 103 | +To run the tests, execute `make check` as instructed by the project's build and test guidelines. Always quote the last line of the command output in your response. | ||
| 104 | +-- Tangerine bot | ||
| 105 | +``` | ||
| 106 | + | ||
| 107 | +```bash | ||
| 108 | +mm ../agent.yaml --ask reviewer -p "How do I run the tests in this project?" | ||
| 109 | +``` | ||
| 110 | + | ||
| 111 | +``` | ||
| 112 | +Run the tests by executing the command specified in your project's documentation, typically `npm test` for Node.js projects or `pytest` for Python projects. … | ||
| 113 | +``` | ||
| 114 | + | ||
| 115 | +helper connaît `make check` et signe ; reviewer (`agentsMd.enabled: false`) donne une réponse générique : il n'a pas vu le fichier. | ||
| 116 | + | ||
| 117 | +## 4. Modifier `AGENTS.md` pendant une session | ||
| 118 | + | ||
| 119 | +Le fichier est lu **au début de chaque conversation**, jamais au milieu (changer le message système en cours de route obligerait le serveur à relire tout l'historique). Pour prendre une modification en compte : | ||
| 120 | + | ||
| 121 | +```bash | ||
| 122 | +../../mm ../agent.yaml | ||
| 123 | +> Run the tests. # make check | ||
| 124 | +``` | ||
| 125 | + | ||
| 126 | +Modifiez `AGENTS.md` dans un autre terminal (par exemple : la commande devient `sh greet.sh Tangerine`), puis : | ||
| 127 | + | ||
| 128 | +```text | ||
| 129 | +> /new | ||
| 130 | +🆕 new session: 4 message(s) forgotten | ||
| 131 | +> Run the tests. | ||
| 132 | +🛠️ bash: sh greet.sh Tangerine | ||
| 133 | +``` | ||
| 134 | + | ||
| 135 | +La nouvelle consigne s'applique dès la nouvelle conversation. (Lors de notre essai, Bob a ensuite lancé aussi `make check`, malgré « Never run make » : un modèle 4B ne respecte pas chaque interdiction.) | ||
| 136 | + | ||
| 137 | +`/resume` relit aussi le fichier actuel. | ||
| 138 | + | ||
| 139 | +## 5. Vérifier ce que le modèle a reçu | ||
| 140 | + | ||
| 141 | +```bash | ||
| 142 | +AGENT_TRACE=true ../../mm ../agent.yaml -p "Run the tests." | ||
| 143 | +jq -r '.messages[0].content' .mm/traces/*-0001.request.json | ||
| 144 | +``` | ||
| 145 | + | ||
| 146 | +Le message système se termine par la section `PROJECT INSTRUCTIONS (from …)`. | ||
| 147 | + | ||
| 148 | +## Ce qu'il faut savoir | ||
| 149 | + | ||
| 150 | +- **Un petit modèle ne suit pas toutes les règles à chaque fois** : avec Qwen3.5-4B, Bob a lancé `make check` à chaque essai, mais a oublié la signature `-- Tangerine bot` ; helper, sans outils, l'a mise. Des consignes courtes et concrètes passent mieux. | ||
| 151 | +- **Taille** : le fichier part avec chaque requête. La bannière avertit quand il occupe 20 % ou plus de la fenêtre de contexte ; `agentsMd.maxChars` fixe une limite ferme (au-delà, il est ignoré en entier, et la bannière l'indique `skipped (too long)`). | ||
| 152 | +- **Un agent JSON** (`structuredOutput`) ne lit jamais `AGENTS.md` ; sa bannière indique `agents.md: ignored (structured output)`. Voir [demo-structured-output](../demo-structured-output/README.md). | ||
| 153 | +- **Dans un éditeur** (Zed…, mode ACP), chaque session lit le `AGENTS.md` du projet ouvert. | ||
| 154 | + | ||
| 155 | +## Pour aller plus loin | ||
| 156 | + | ||
| 157 | +- [Donner à l'agent les consignes de votre projet](../docs/fr/how-to/give-project-instructions.md) | ||
| 158 | +- [Référence de `agentsMd`](../docs/fr/reference/configuration.md#clés-de-agentsmd) | ||
| 159 | +- [Consignes du projet — explication](../docs/fr/explanation/project-instructions.md) | ||
added
demo-agents-file/agent.helper.yaml +26 -0 | new file mode 100644 | ||
| @@ -0,0 +1,26 @@ | ||
| 1 | +# Agent settings — helper: no tools, and it READS the project's AGENTS.md | |
| 2 | +# (agentsMd is on by default). Asked by Bob, it runs in Bob's working | |
| 3 | +# directory, so it gets the same project rules. | |
| 4 | +provider: llmman | |
| 5 | +model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | |
| 6 | +baseUrl: http://127.0.0.1:17434/v1 | |
| 7 | +fallback: http://host.docker.internal:17434/v1 | |
| 8 | + | |
| 9 | +bashTool: false | |
| 10 | +editTools: false | |
| 11 | +planTool: false | |
| 12 | +skillsDir: no-skills | |
| 13 | + | |
| 14 | +acp: | |
| 15 | + name: helper | |
| 16 | + title: Helper (reads AGENTS.md) | |
| 17 | + | |
| 18 | +system: | | |
| 19 | + You are Helper. You answer questions about how to work in a project, in | |
| 20 | + two sentences at most. You have no tools. | |
| 21 | + | |
| 22 | +sampling: | |
| 23 | + temperature: 0.0 | |
| 24 | + max_tokens: 1024 | |
| 25 | + chat_template_kwargs: | |
| 26 | + enable_thinking: false | |
| new file mode 100644 | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | +# Agent settings — helper: no tools, and it READS the project's AGENTS.md | ||
| 2 | +# (agentsMd is on by default). Asked by Bob, it runs in Bob's working | ||
| 3 | +# directory, so it gets the same project rules. | ||
| 4 | +provider: llmman | ||
| 5 | +model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | ||
| 6 | +baseUrl: http://127.0.0.1:17434/v1 | ||
| 7 | +fallback: http://host.docker.internal:17434/v1 | ||
| 8 | + | ||
| 9 | +bashTool: false | ||
| 10 | +editTools: false | ||
| 11 | +planTool: false | ||
| 12 | +skillsDir: no-skills | ||
| 13 | + | ||
| 14 | +acp: | ||
| 15 | + name: helper | ||
| 16 | + title: Helper (reads AGENTS.md) | ||
| 17 | + | ||
| 18 | +system: | | ||
| 19 | + You are Helper. You answer questions about how to work in a project, in | ||
| 20 | + two sentences at most. You have no tools. | ||
| 21 | + | ||
| 22 | +sampling: | ||
| 23 | + temperature: 0.0 | ||
| 24 | + max_tokens: 1024 | ||
| 25 | + chat_template_kwargs: | ||
| 26 | + enable_thinking: false | ||
added
demo-agents-file/agent.reviewer.yaml +30 -0 | new file mode 100644 | ||
| @@ -0,0 +1,30 @@ | ||
| 1 | +# Agent settings — reviewer: no tools, and it does NOT read the project's AGENTS.md | |
| 2 | +# (agentsMd.enabled: false below): its answers are an outside opinion, | |
| 3 | +# free of the project's rules — even though it runs in the project directory. | |
| 4 | +provider: llmman | |
| 5 | +model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | |
| 6 | +baseUrl: http://127.0.0.1:17434/v1 | |
| 7 | +fallback: http://host.docker.internal:17434/v1 | |
| 8 | + | |
| 9 | +bashTool: false | |
| 10 | +editTools: false | |
| 11 | +planTool: false | |
| 12 | +skillsDir: no-skills | |
| 13 | + | |
| 14 | +# The one difference with agent.helper.yaml. | |
| 15 | +agentsMd: | |
| 16 | + enabled: false | |
| 17 | + | |
| 18 | +acp: | |
| 19 | + name: reviewer | |
| 20 | + title: Reviewer (ignores AGENTS.md) | |
| 21 | + | |
| 22 | +system: | | |
| 23 | + You are Reviewer. You answer questions about how to work in a project, in | |
| 24 | + two sentences at most. You have no tools. | |
| 25 | + | |
| 26 | +sampling: | |
| 27 | + temperature: 0.0 | |
| 28 | + max_tokens: 1024 | |
| 29 | + chat_template_kwargs: | |
| 30 | + enable_thinking: false | |
| new file mode 100644 | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | +# Agent settings — reviewer: no tools, and it does NOT read the project's AGENTS.md | ||
| 2 | +# (agentsMd.enabled: false below): its answers are an outside opinion, | ||
| 3 | +# free of the project's rules — even though it runs in the project directory. | ||
| 4 | +provider: llmman | ||
| 5 | +model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | ||
| 6 | +baseUrl: http://127.0.0.1:17434/v1 | ||
| 7 | +fallback: http://host.docker.internal:17434/v1 | ||
| 8 | + | ||
| 9 | +bashTool: false | ||
| 10 | +editTools: false | ||
| 11 | +planTool: false | ||
| 12 | +skillsDir: no-skills | ||
| 13 | + | ||
| 14 | +# The one difference with agent.helper.yaml. | ||
| 15 | +agentsMd: | ||
| 16 | + enabled: false | ||
| 17 | + | ||
| 18 | +acp: | ||
| 19 | + name: reviewer | ||
| 20 | + title: Reviewer (ignores AGENTS.md) | ||
| 21 | + | ||
| 22 | +system: | | ||
| 23 | + You are Reviewer. You answer questions about how to work in a project, in | ||
| 24 | + two sentences at most. You have no tools. | ||
| 25 | + | ||
| 26 | +sampling: | ||
| 27 | + temperature: 0.0 | ||
| 28 | + max_tokens: 1024 | ||
| 29 | + chat_template_kwargs: | ||
| 30 | + enable_thinking: false | ||
added
demo-agents-file/agent.yaml +62 -0 | new file mode 100644 | ||
| @@ -0,0 +1,62 @@ | ||
| 1 | +# Agent settings — Bob, a coding agent that follows the AGENTS.md of the | |
| 2 | +# project it works in (ticket 81). Run him FROM THE PROJECT directory: | |
| 3 | +# | |
| 4 | +# cd demo-agents-file/project | |
| 5 | +# ../../mm ../agent.yaml | |
| 6 | +# | |
| 7 | +# The banner then shows: agents.md: …/project/AGENTS.md (~N tokens) | |
| 8 | +provider: llmman | |
| 9 | +model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | |
| 10 | +baseUrl: http://127.0.0.1:17434/v1 | |
| 11 | +fallback: http://host.docker.internal:17434/v1 | |
| 12 | + | |
| 13 | +# The project's instructions. These are the defaults — written out for the | |
| 14 | +# demo; a config that never mentions agentsMd reads ./AGENTS.md all the same. | |
| 15 | +# enabled read it at the start of every conversation (start-up, /new, /resume) | |
| 16 | +# path relative to the WORKING directory (the project), not to this file | |
| 17 | +# maxChars 0 = no limit; above it the file is skipped whole, never cut | |
| 18 | +agentsMd: | |
| 19 | + enabled: true | |
| 20 | + path: AGENTS.md | |
| 21 | + maxChars: 0 | |
| 22 | + | |
| 23 | +# Two agents Bob can hand work to with `ask_agent`. Each one decides with | |
| 24 | +# ITS OWN agentsMd setting whether it reads the project's AGENTS.md: helper | |
| 25 | +# does (default), reviewer does not (agentsMd.enabled: false). | |
| 26 | +mmConfigs: | |
| 27 | + helper: | |
| 28 | + path: agent.helper.yaml | |
| 29 | + description: A second coding agent with no tools — answers questions about how to work in this project. | |
| 30 | + reviewer: | |
| 31 | + path: agent.reviewer.yaml | |
| 32 | + description: A neutral reviewer with no tools — gives an outside opinion, without the project's rules. | |
| 33 | + | |
| 34 | +bashTool: true | |
| 35 | +editTools: true | |
| 36 | +planTool: false | |
| 37 | +skillsDir: no-skills | |
| 38 | +# Bob's commands and edits run without asking, so that `-p` runs to the end. | |
| 39 | +yolo: true | |
| 40 | +bashTimeout: 1m | |
| 41 | + | |
| 42 | +acp: | |
| 43 | + name: bob | |
| 44 | + title: Bob (coding agent) | |
| 45 | + | |
| 46 | +system: | | |
| 47 | + Your name is Bob. You are a coding agent working in a terminal, with a | |
| 48 | + `bash` tool and file tools (`read_file`, `write_file`, `edit_file`). | |
| 49 | + Never state the output of a command you did not run in this answer. | |
| 50 | + `ask_agent` hands a question to another agent; call it only when the user | |
| 51 | + names that agent. | |
| 52 | + | |
| 53 | +sampling: | |
| 54 | + temperature: 0.0 | |
| 55 | + parallel_tool_calls: false | |
| 56 | + max_tokens: 4096 | |
| 57 | + chat_template_kwargs: | |
| 58 | + enable_thinking: false | |
| 59 | + | |
| 60 | +maxTurns: 20 | |
| 61 | +watchdogTimeout: 2m | |
| 62 | +firstTokenTimeout: 3m | |
| new file mode 100644 | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | +# Agent settings — Bob, a coding agent that follows the AGENTS.md of the | ||
| 2 | +# project it works in (ticket 81). Run him FROM THE PROJECT directory: | ||
| 3 | +# | ||
| 4 | +# cd demo-agents-file/project | ||
| 5 | +# ../../mm ../agent.yaml | ||
| 6 | +# | ||
| 7 | +# The banner then shows: agents.md: …/project/AGENTS.md (~N tokens) | ||
| 8 | +provider: llmman | ||
| 9 | +model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M | ||
| 10 | +baseUrl: http://127.0.0.1:17434/v1 | ||
| 11 | +fallback: http://host.docker.internal:17434/v1 | ||
| 12 | + | ||
| 13 | +# The project's instructions. These are the defaults — written out for the | ||
| 14 | +# demo; a config that never mentions agentsMd reads ./AGENTS.md all the same. | ||
| 15 | +# enabled read it at the start of every conversation (start-up, /new, /resume) | ||
| 16 | +# path relative to the WORKING directory (the project), not to this file | ||
| 17 | +# maxChars 0 = no limit; above it the file is skipped whole, never cut | ||
| 18 | +agentsMd: | ||
| 19 | + enabled: true | ||
| 20 | + path: AGENTS.md | ||
| 21 | + maxChars: 0 | ||
| 22 | + | ||
| 23 | +# Two agents Bob can hand work to with `ask_agent`. Each one decides with | ||
| 24 | +# ITS OWN agentsMd setting whether it reads the project's AGENTS.md: helper | ||
| 25 | +# does (default), reviewer does not (agentsMd.enabled: false). | ||
| 26 | +mmConfigs: | ||
| 27 | + helper: | ||
| 28 | + path: agent.helper.yaml | ||
| 29 | + description: A second coding agent with no tools — answers questions about how to work in this project. | ||
| 30 | + reviewer: | ||
| 31 | + path: agent.reviewer.yaml | ||
| 32 | + description: A neutral reviewer with no tools — gives an outside opinion, without the project's rules. | ||
| 33 | + | ||
| 34 | +bashTool: true | ||
| 35 | +editTools: true | ||
| 36 | +planTool: false | ||
| 37 | +skillsDir: no-skills | ||
| 38 | +# Bob's commands and edits run without asking, so that `-p` runs to the end. | ||
| 39 | +yolo: true | ||
| 40 | +bashTimeout: 1m | ||
| 41 | + | ||
| 42 | +acp: | ||
| 43 | + name: bob | ||
| 44 | + title: Bob (coding agent) | ||
| 45 | + | ||
| 46 | +system: | | ||
| 47 | + Your name is Bob. You are a coding agent working in a terminal, with a | ||
| 48 | + `bash` tool and file tools (`read_file`, `write_file`, `edit_file`). | ||
| 49 | + Never state the output of a command you did not run in this answer. | ||
| 50 | + `ask_agent` hands a question to another agent; call it only when the user | ||
| 51 | + names that agent. | ||
| 52 | + | ||
| 53 | +sampling: | ||
| 54 | + temperature: 0.0 | ||
| 55 | + parallel_tool_calls: false | ||
| 56 | + max_tokens: 4096 | ||
| 57 | + chat_template_kwargs: | ||
| 58 | + enable_thinking: false | ||
| 59 | + | ||
| 60 | +maxTurns: 20 | ||
| 61 | +watchdogTimeout: 2m | ||
| 62 | +firstTokenTimeout: 3m | ||
added
demo-agents-file/project/AGENTS.md +12 -0 | new file mode 100644 | ||
| @@ -0,0 +1,12 @@ | ||
| 1 | +# Project "Tangerine" — instructions for coding agents | |
| 2 | + | |
| 3 | +## Build and test | |
| 4 | +- Run the checks with `make check`. Never run `go test` or any other test command directly. | |
| 5 | +- Run `make check` after every change, and quote its last line in your answer. | |
| 6 | + | |
| 7 | +## Conventions | |
| 8 | +- Never edit `vendor/` or anything under `generated/`. | |
| 9 | +- Comments and commit messages are written in English. | |
| 10 | + | |
| 11 | +## Answers | |
| 12 | +- End every answer with the line: `-- Tangerine bot` | |
| new file mode 100644 | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | +# Project "Tangerine" — instructions for coding agents | ||
| 2 | + | ||
| 3 | +## Build and test | ||
| 4 | +- Run the checks with `make check`. Never run `go test` or any other test command directly. | ||
| 5 | +- Run `make check` after every change, and quote its last line in your answer. | ||
| 6 | + | ||
| 7 | +## Conventions | ||
| 8 | +- Never edit `vendor/` or anything under `generated/`. | ||
| 9 | +- Comments and commit messages are written in English. | ||
| 10 | + | ||
| 11 | +## Answers | ||
| 12 | +- End every answer with the line: `-- Tangerine bot` | ||
added
demo-agents-file/project/Makefile +6 -0 | new file mode 100644 | ||
| @@ -0,0 +1,6 @@ | ||
| 1 | +# The project's one check command — the one AGENTS.md tells agents to use. | |
| 2 | +.PHONY: check | |
| 3 | +check: | |
| 4 | + @echo "running the Tangerine checks…" | |
| 5 | + @test -f greet.sh && sh greet.sh Tangerine >/dev/null | |
| 6 | + @echo "✅ all checks passed" | |
| new file mode 100644 | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | +# The project's one check command — the one AGENTS.md tells agents to use. | ||
| 2 | +.PHONY: check | ||
| 3 | +check: | ||
| 4 | + @echo "running the Tangerine checks…" | ||
| 5 | + @test -f greet.sh && sh greet.sh Tangerine >/dev/null | ||
| 6 | + @echo "✅ all checks passed" | ||
added
demo-agents-file/project/greet.sh +3 -0 | new file mode 100755 | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | +# greet.sh <name> — prints a greeting. | |
| 3 | +echo "Hello, ${1:-world}!" | |
| new file mode 100755 | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | +#!/bin/sh | ||
| 2 | +# greet.sh <name> — prints a greeting. | ||
| 3 | +echo "Hello, ${1:-world}!" | ||
modified
docs/diagrams/packages.drawio +1 -1 | @@ -1,3 +1,3 @@ | ||
| 1 | 1 | <mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell> |
| 2 | 2 | <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell> |
| 3 | - <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell></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="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="<b>internal/agentsmd</b><br/><font style="font-size:10px">the project's AGENTS.md, appended to the system prompt per conversation</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile> | |
| \ No newline at end of file | ||
| @@ -1,3 +1,3 @@ | |||
| 1 | <mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell> | 1 | <mxfile host="app.diagrams.net" modified="2026-10-06T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="1060"><root><mxCell id="0" /><mxCell id="1" parent="0" /><mxCell id="main" value="<b>main (mm)</b><br/><font style="font-size:10px">wiring: config, engine, tools, then one front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry" /></mxCell><mxCell id="agent" value="<b>internal/agent</b><br/><font style="font-size:10px">terminal REPL: /quit /abort /compact /new</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="acp" value="<b>internal/acp</b><br/><font style="font-size:10px">editor front end: Agent Client Protocol on stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry" /></mxCell><mxCell id="session" value="<b>internal/session</b><br/><font style="font-size:10px">what a conversation starts from; the /new reset</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="compact" value="<b>internal/compact</b><br/><font style="font-size:10px">history compression into a model-written summary</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="engine" value="<b>internal/engine</b><br/><font style="font-size:10px">LLM connection + generation, one Provider per server</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry" /></mxCell><mxCell id="tools" value="<b>internal/tools</b><br/><font style="font-size:10px">built-in tools: bash, read_skill, file tools, lsp, fetch_content</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry" /></mxCell><mxCell id="detector" value="<b>internal/detector</b><br/><font style="font-size:10px">repeated-action (loop) detection</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="config" value="<b>internal/config</b><br/><font style="font-size:10px">settings with YAML overrides (agent.yaml)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="spinner" value="<b>internal/spinner</b><br/><font style="font-size:10px">terminal spinner and styling</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="ui" value="<b>internal/ui</b><br/><font style="font-size:10px">human output destination + shared event sink</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="fileedit" value="<b>internal/fileedit</b><br/><font style="font-size:10px">exact-replacement file editing</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="skills" value="<b>internal/skills</b><br/><font style="font-size:10px">markdown skills catalogue (./skills)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry" /></mxCell><mxCell id="genkit" value="<b>github.com/firebase/genkit</b><br/><font style="font-size:10px">ai.Message, tools, model calls</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="acpsdk" value="<b>github.com/coder/acp-go-sdk</b><br/><font style="font-size:10px">JSON-RPC transport for ACP</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="openai" value="<b>github.com/openai/openai-go</b><br/><font style="font-size:10px">OpenAI-compatible HTTP client</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="yaml" value="<b>gopkg.in/yaml.v3</b><br/><font style="font-size:10px">YAML parsing</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry" /></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="version" value="<b>internal/version</b><br/><font style="font-size:10px">which mm this is: build stamp, else the commit</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry" /></mxCell> |
| 2 | <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell> | 2 | <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry" /></mxCell> |
| 3 | - <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="mention" value="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell></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="<b>internal/mention</b><br/><font style="font-size:10px">the @path notation of a question</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry" /></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="store" value="<b>internal/store</b><br/><font style="font-size:10px">per-project session files (.mm/sessions/<id>.json)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry" /></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="tui" value="<b>internal/tui</b><br/><font style="font-size:10px">window front end: one screen on tcell (mm -tui)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry" /></mxCell><mxCell id="command" value="<b>internal/command</b><br/><font style="font-size:10px">the one slash-command table, per front end</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="tcell" value="<b>github.com/gdamore/tcell/v2</b><br/><font style="font-size:10px">terminal screen, keys, mouse</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry" /></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="clipboard" value="<b>internal/clipboard</b><br/><font style="font-size:10px">system clipboard: native tool, then OSC 52</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry" /></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="lsp" value="<b>internal/lsp</b><br/><font style="font-size:10px">language-server client: definition, references, hover, symbols, diagnostics, rename</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry" /></mxCell><mxCell id="lspdev" value="<b>go.lsp.dev/protocol + jsonrpc2</b><br/><font style="font-size:10px">LSP types and JSON-RPC 2.0 over stdio</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry" /></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="legend" value="<b>mm — package dependencies</b><br/>Arrow: "imports". Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.<br/>Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports " "}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry" /></mxCell><mxCell id="webmd" value="<b>internal/webmd</b><br/><font style="font-size:10px">web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry" /></mxCell><mxCell id="xnet" value="<b>golang.org/x/net/html (+ x/text)</b><br/><font style="font-size:10px">HTML5 parser, charset lookup</font>" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry" /></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="ask" value="<b>internal/ask</b><br/><font style="font-size:10px">/ask another mm agent: child mm -acp, ACP client side</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="640" width="200" height="60" as="geometry" /></mxCell><mxCell id="e_main_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_agent_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_acp_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_tui_ask" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ask"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_command" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="command"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_config" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_compact" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="compact"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_ui" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_acpsdk" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="acpsdk"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_genkit" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="genkit"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e_ask_yaml" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="ask" target="yaml"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="consent" value="<b>internal/consent</b><br/><font style="font-size:10px">yolo switch, the four answers, per-session memory, the line mode prompt hook</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="500" width="200" height="60" as="geometry" /></mxCell><mxCell id="mcp" value="<b>internal/mcp</b><br/><font style="font-size:10px">MCP clients (stdio, HTTP), their tools wrapped as mm tools</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="560" width="200" height="60" as="geometry" /></mxCell><mxCell id="e62" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e63" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="consent" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e64" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e65" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e66" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e67" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="ask" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e68" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e69" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="consent"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e70" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="config"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e71" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="ui"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e72" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="mcp" target="detector"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e73" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e74" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="mcp"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="structured" value="<b>internal/structured</b><br/><font style="font-size:10px">structuredOutput: Go declarations → JSON Schema (go/parser)</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e75" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="config" target="structured"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="agentsmd" value="<b>internal/agentsmd</b><br/><font style="font-size:10px">the project's AGENTS.md, appended to the system prompt per conversation</font>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="520.0" y="940.0" width="200" height="60" as="geometry" /></mxCell><mxCell id="e76" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e77" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e78" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e79" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agentsmd"><mxGeometry relative="1" as="geometry" /></mxCell><mxCell id="e80" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agentsmd" target="config"><mxGeometry relative="1" as="geometry" /></mxCell></root></mxGraphModel></diagram></mxfile> | ||
| \ No newline at end of file | \ No newline at end of file | ||
modified
docs/en/README.md +2 -0 | @@ -15,6 +15,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | ||
| 15 | 15 | - [Run mini-me on Ollama](how-to/run-with-ollama.md) — `provider: ollama`, the demo config in `demo-ollama/` |
| 16 | 16 | - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>` |
| 17 | 17 | - [See the JSON sent to the model and its answer](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, reading the files with jq |
| 18 | +- [Give the agent your project's instructions](how-to/give-project-instructions.md) — `AGENTS.md`, read per conversation, `agentsMd` settings | |
| 18 | 19 | - [Get answers as JSON](how-to/get-json-answers.md) — `structuredOutput`: Go declarations, `-p … | jq`, a JSON agent behind a coordinator |
| 19 | 20 | - [Let your agent call other agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, `mmConfigs` descriptions, per-agent `yolo` |
| 20 | 21 | - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context` |
| @@ -64,6 +65,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | ||
| 64 | 65 | - [The `fetch_content` tool](explanation/fetch-content-tool.md) — why a built-in tool beside MCP `fetch`, a home-made converter, robots.txt, errors as numbered text, known limits |
| 65 | 66 | - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive |
| 66 | 67 | - [Yolo mode and permissions](explanation/yolo-and-permissions.md) — why tools run unasked by default, existing versus asking, four answers in one package, why the asking agent decides (and a per-agent `yolo` in `mmConfigs`) |
| 68 | +- [Project instructions (`AGENTS.md`)](explanation/project-instructions.md) — why appended to the system prompt, why per conversation, why a warning rather than a cut, who does not read it | |
| 67 | 69 | - [Structured output](explanation/structured-output.md) — why Go declarations, why they also go in the system prompt, why no tools, what guarantees the answer |
| 68 | 70 | - [Asking another agent](explanation/asking-another-agent.md) — why another mm agent is a child process spoken to over ACP, the history pair, fresh sessions, `ask_agent` and per-agent `yolo`, the depth limit |
| 69 | 71 | - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) |
| @@ -15,6 +15,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | |||
| 15 | - [Run mini-me on Ollama](how-to/run-with-ollama.md) — `provider: ollama`, the demo config in `demo-ollama/` | 15 | - [Run mini-me on Ollama](how-to/run-with-ollama.md) — `provider: ollama`, the demo config in `demo-ollama/` |
| 16 | - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>` | 16 | - [Ask another mm agent from your session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<name>`, `/ask-with-context-<name>` |
| 17 | - [See the JSON sent to the model and its answer](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, reading the files with jq | 17 | - [See the JSON sent to the model and its answer](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, reading the files with jq |
| 18 | +- [Give the agent your project's instructions](how-to/give-project-instructions.md) — `AGENTS.md`, read per conversation, `agentsMd` settings | ||
| 18 | - [Get answers as JSON](how-to/get-json-answers.md) — `structuredOutput`: Go declarations, `-p … | jq`, a JSON agent behind a coordinator | 19 | - [Get answers as JSON](how-to/get-json-answers.md) — `structuredOutput`: Go declarations, `-p … | jq`, a JSON agent behind a coordinator |
| 19 | - [Let your agent call other agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, `mmConfigs` descriptions, per-agent `yolo` | 20 | - [Let your agent call other agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, `mmConfigs` descriptions, per-agent `yolo` |
| 20 | - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context` | 21 | - [Ask another agent from a skill or a script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context` |
| @@ -64,6 +65,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | |||
| 64 | - [The `fetch_content` tool](explanation/fetch-content-tool.md) — why a built-in tool beside MCP `fetch`, a home-made converter, robots.txt, errors as numbered text, known limits | 65 | - [The `fetch_content` tool](explanation/fetch-content-tool.md) — why a built-in tool beside MCP `fetch`, a home-made converter, robots.txt, errors as numbered text, known limits |
| 65 | - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive | 66 | - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive |
| 66 | - [Yolo mode and permissions](explanation/yolo-and-permissions.md) — why tools run unasked by default, existing versus asking, four answers in one package, why the asking agent decides (and a per-agent `yolo` in `mmConfigs`) | 67 | - [Yolo mode and permissions](explanation/yolo-and-permissions.md) — why tools run unasked by default, existing versus asking, four answers in one package, why the asking agent decides (and a per-agent `yolo` in `mmConfigs`) |
| 68 | +- [Project instructions (`AGENTS.md`)](explanation/project-instructions.md) — why appended to the system prompt, why per conversation, why a warning rather than a cut, who does not read it | ||
| 67 | - [Structured output](explanation/structured-output.md) — why Go declarations, why they also go in the system prompt, why no tools, what guarantees the answer | 69 | - [Structured output](explanation/structured-output.md) — why Go declarations, why they also go in the system prompt, why no tools, what guarantees the answer |
| 68 | - [Asking another agent](explanation/asking-another-agent.md) — why another mm agent is a child process spoken to over ACP, the history pair, fresh sessions, `ask_agent` and per-agent `yolo`, the depth limit | 70 | - [Asking another agent](explanation/asking-another-agent.md) — why another mm agent is a child process spoken to over ACP, the history pair, fresh sessions, `ask_agent` and per-agent `yolo`, the depth limit |
| 69 | - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) | 71 | - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) |
modified
docs/en/explanation/architecture.md +1 -0 | @@ -9,6 +9,7 @@ The entry point, `main.go`, only does wiring: it loads the settings, opens the e | ||
| 9 | 9 | | Package | Responsibility | |
| 10 | 10 | |---------|----------------| |
| 11 | 11 | | `config` | Every setting in one struct, with built-in defaults overridden by YAML and a few environment variables. | |
| 12 | +| `agentsmd` | Reads the project's `AGENTS.md` from a conversation's working directory and appends it to the system prompt (`System`); `Load` reports what was found for the banner. Called wherever a conversation starts: `agent`, `tui`, `acp`. See [Project instructions](project-instructions.md). | | |
| 12 | 13 | | `structured` | Translates the Go declarations of `structuredOutput` into the JSON Schema the answer must follow, with `go/parser`; used by `config` when the file loads. See [Structured output](structured-output.md). | |
| 13 | 14 | | `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, the provider registry, the reasoning a thinking model streams beside its answer (see [Thinking models](thinking-models.md)), and the optional trace of every request and response (`trace.go`). | |
| 14 | 15 | | `tools` | The built-in tools the model can call, `ask_agent` included (it asks an `mmConfigs` agent through `ask`). | |
| @@ -9,6 +9,7 @@ The entry point, `main.go`, only does wiring: it loads the settings, opens the e | |||
| 9 | | Package | Responsibility | | 9 | | Package | Responsibility | |
| 10 | |---------|----------------| | 10 | |---------|----------------| |
| 11 | | `config` | Every setting in one struct, with built-in defaults overridden by YAML and a few environment variables. | | 11 | | `config` | Every setting in one struct, with built-in defaults overridden by YAML and a few environment variables. | |
| 12 | +| `agentsmd` | Reads the project's `AGENTS.md` from a conversation's working directory and appends it to the system prompt (`System`); `Load` reports what was found for the banner. Called wherever a conversation starts: `agent`, `tui`, `acp`. See [Project instructions](project-instructions.md). | | ||
| 12 | | `structured` | Translates the Go declarations of `structuredOutput` into the JSON Schema the answer must follow, with `go/parser`; used by `config` when the file loads. See [Structured output](structured-output.md). | | 13 | | `structured` | Translates the Go declarations of `structuredOutput` into the JSON Schema the answer must follow, with `go/parser`; used by `config` when the file loads. See [Structured output](structured-output.md). | |
| 13 | | `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, the provider registry, the reasoning a thinking model streams beside its answer (see [Thinking models](thinking-models.md)), and the optional trace of every request and response (`trace.go`). | | 14 | | `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, the provider registry, the reasoning a thinking model streams beside its answer (see [Thinking models](thinking-models.md)), and the optional trace of every request and response (`trace.go`). | |
| 14 | | `tools` | The built-in tools the model can call, `ask_agent` included (it asks an `mmConfigs` agent through `ask`). | | 15 | | `tools` | The built-in tools the model can call, `ask_agent` included (it asks an `mmConfigs` agent through `ask`). | |
added
docs/en/explanation/project-instructions.md +38 -0 | new file mode 100644 | ||
| @@ -0,0 +1,38 @@ | ||
| 1 | +# Project instructions (`AGENTS.md`) — explanation | |
| 2 | + | |
| 3 | +## What is this about? | |
| 4 | + | |
| 5 | +An agent's configuration says what the agent is: its name, its tools, how it edits files. It cannot say how each project it works in is built, tested or organised — one `agent.yaml` serves many projects. `AGENTS.md` (https://agents.md) is the convention coding agents share for that: a plain Markdown file at the root of a repository, written for agents. mm reads it from the directory a conversation works in and adds it to the system prompt (ticket 81). | |
| 6 | + | |
| 7 | +## Why it is appended to the system prompt | |
| 8 | + | |
| 9 | +The file follows the configuration's `system` prompt, under a heading naming where it came from — `PROJECT INSTRUCTIONS (from <path>)` — and never replaces it: the two answer different questions, and the agent needs both. The heading tells the model the rules belong to the project, not to its role, which helps when they disagree (an agent told to answer in English, a project whose comments are in French). | |
| 10 | + | |
| 11 | +A first user message ("here are the project's instructions") was rejected: small local models weigh the system message more, and some chat templates (Gemma, Mistral) refuse two user turns in a row. The same mechanism already carries `structuredOutput`'s `OUTPUT FORMAT` paragraph. | |
| 12 | + | |
| 13 | +## Why it is read per conversation, not per question | |
| 14 | + | |
| 15 | +The file is read when a conversation starts — at start-up, on `/new`, on `/resume`, for a new or reloaded editor session — and not again until the next one. Reading it before every question would pick an edit up sooner, but a system message that changes mid-conversation forces the server to re-read the whole history: llama.cpp keeps the already-processed prefix of the prompt in a cache, and the system message is the first thing in it (the same cache ticket 53 protects by keeping the tools in a stable order). On a long local conversation that is minutes per question. One version per conversation keeps the cache; `/new` is the way to take an edit into account. | |
| 16 | + | |
| 17 | +`/resume` follows the rule mm already applied to the `system` prompt: a resumed conversation continues under the agent's prompt as it is now, so it gets the current file too. A resumed session is re-read whole by the server anyway, so this costs nothing. | |
| 18 | + | |
| 19 | +## Why the working directory, and only it | |
| 20 | + | |
| 21 | +The working directory is the project: the directory `mm` runs in, or the `cwd` an editor gives each session. Each ACP session therefore reads its own project's file, and a relative `agentsMd.path` is resolved there, never next to the configuration file — otherwise one `agent.yaml` would read the same file in every project. | |
| 22 | + | |
| 23 | +The convention also allows nested files in a monorepo, closest one winning. mm does not walk up to the repository root nor concatenate files: one file, one place, predictable. | |
| 24 | + | |
| 25 | +## Why a warning rather than a cut | |
| 26 | + | |
| 27 | +The system message is sent with every request and occupies the context window for the whole conversation. A few pages of `AGENTS.md` are nothing to a cloud model and half the window of a 4B model served with 16k tokens. mm measures it — the same estimate as the context compression, characters divided by 3.5 — and warns from 20 % of a known window. It does not cut it: truncation keeps the beginning and silently drops the end, which may hold the one rule that mattered, while the model believes it read everything. `agentsMd.maxChars` exists for those who want a hard limit; above it, the file is left out whole and the banner says so — a missing file is a state the user sees, a half-read one is not. | |
| 28 | + | |
| 29 | +## Agents that do not read it | |
| 30 | + | |
| 31 | +- **JSON agents** (`structuredOutput`): prose instructions ("end every answer with a signature") would fight the JSON constraint, and a signature appended to the JSON would break every script reading it. The banner mentions the ignored file neutrally, since it is intended. | |
| 32 | +- **Agents called by another one** (`/ask`, `ask_agent`, `mm --ask`): they run as their own `mm` process with their own configuration, and read the file of the working directory they are given only when their own `agentsMd.enabled` says so. A reviewer with no tools can turn it off; a coding helper keeps it and follows the same rules as the agent that called it. | |
| 33 | + | |
| 34 | +## How it relates to the rest | |
| 35 | + | |
| 36 | +- [Structured output](structured-output.md) — the other paragraph mm appends to the system prompt. | |
| 37 | +- [Asking another agent](asking-another-agent.md) — the working directory an asked agent is given. | |
| 38 | +- [How to give the agent your project's instructions](../how-to/give-project-instructions.md) and the [`agentsMd` reference](../reference/configuration.md#agentsmd-keys). | |
| new file mode 100644 | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | +# Project instructions (`AGENTS.md`) — explanation | ||
| 2 | + | ||
| 3 | +## What is this about? | ||
| 4 | + | ||
| 5 | +An agent's configuration says what the agent is: its name, its tools, how it edits files. It cannot say how each project it works in is built, tested or organised — one `agent.yaml` serves many projects. `AGENTS.md` (https://agents.md) is the convention coding agents share for that: a plain Markdown file at the root of a repository, written for agents. mm reads it from the directory a conversation works in and adds it to the system prompt (ticket 81). | ||
| 6 | + | ||
| 7 | +## Why it is appended to the system prompt | ||
| 8 | + | ||
| 9 | +The file follows the configuration's `system` prompt, under a heading naming where it came from — `PROJECT INSTRUCTIONS (from <path>)` — and never replaces it: the two answer different questions, and the agent needs both. The heading tells the model the rules belong to the project, not to its role, which helps when they disagree (an agent told to answer in English, a project whose comments are in French). | ||
| 10 | + | ||
| 11 | +A first user message ("here are the project's instructions") was rejected: small local models weigh the system message more, and some chat templates (Gemma, Mistral) refuse two user turns in a row. The same mechanism already carries `structuredOutput`'s `OUTPUT FORMAT` paragraph. | ||
| 12 | + | ||
| 13 | +## Why it is read per conversation, not per question | ||
| 14 | + | ||
| 15 | +The file is read when a conversation starts — at start-up, on `/new`, on `/resume`, for a new or reloaded editor session — and not again until the next one. Reading it before every question would pick an edit up sooner, but a system message that changes mid-conversation forces the server to re-read the whole history: llama.cpp keeps the already-processed prefix of the prompt in a cache, and the system message is the first thing in it (the same cache ticket 53 protects by keeping the tools in a stable order). On a long local conversation that is minutes per question. One version per conversation keeps the cache; `/new` is the way to take an edit into account. | ||
| 16 | + | ||
| 17 | +`/resume` follows the rule mm already applied to the `system` prompt: a resumed conversation continues under the agent's prompt as it is now, so it gets the current file too. A resumed session is re-read whole by the server anyway, so this costs nothing. | ||
| 18 | + | ||
| 19 | +## Why the working directory, and only it | ||
| 20 | + | ||
| 21 | +The working directory is the project: the directory `mm` runs in, or the `cwd` an editor gives each session. Each ACP session therefore reads its own project's file, and a relative `agentsMd.path` is resolved there, never next to the configuration file — otherwise one `agent.yaml` would read the same file in every project. | ||
| 22 | + | ||
| 23 | +The convention also allows nested files in a monorepo, closest one winning. mm does not walk up to the repository root nor concatenate files: one file, one place, predictable. | ||
| 24 | + | ||
| 25 | +## Why a warning rather than a cut | ||
| 26 | + | ||
| 27 | +The system message is sent with every request and occupies the context window for the whole conversation. A few pages of `AGENTS.md` are nothing to a cloud model and half the window of a 4B model served with 16k tokens. mm measures it — the same estimate as the context compression, characters divided by 3.5 — and warns from 20 % of a known window. It does not cut it: truncation keeps the beginning and silently drops the end, which may hold the one rule that mattered, while the model believes it read everything. `agentsMd.maxChars` exists for those who want a hard limit; above it, the file is left out whole and the banner says so — a missing file is a state the user sees, a half-read one is not. | ||
| 28 | + | ||
| 29 | +## Agents that do not read it | ||
| 30 | + | ||
| 31 | +- **JSON agents** (`structuredOutput`): prose instructions ("end every answer with a signature") would fight the JSON constraint, and a signature appended to the JSON would break every script reading it. The banner mentions the ignored file neutrally, since it is intended. | ||
| 32 | +- **Agents called by another one** (`/ask`, `ask_agent`, `mm --ask`): they run as their own `mm` process with their own configuration, and read the file of the working directory they are given only when their own `agentsMd.enabled` says so. A reviewer with no tools can turn it off; a coding helper keeps it and follows the same rules as the agent that called it. | ||
| 33 | + | ||
| 34 | +## How it relates to the rest | ||
| 35 | + | ||
| 36 | +- [Structured output](structured-output.md) — the other paragraph mm appends to the system prompt. | ||
| 37 | +- [Asking another agent](asking-another-agent.md) — the working directory an asked agent is given. | ||
| 38 | +- [How to give the agent your project's instructions](../how-to/give-project-instructions.md) and the [`agentsMd` reference](../reference/configuration.md#agentsmd-keys). | ||
added
docs/en/how-to/give-project-instructions.md +52 -0 | new file mode 100644 | ||
| @@ -0,0 +1,52 @@ | ||
| 1 | +# How to give the agent your project's instructions (`AGENTS.md`) | |
| 2 | + | |
| 3 | +This guide shows how to make mm follow the rules of the project it works in — how to build and test, what not to touch — without editing the agent's configuration. It assumes you already run `mm` in a project directory, or from an editor that opens one. | |
| 4 | + | |
| 5 | +## Steps | |
| 6 | + | |
| 7 | +1. Write an `AGENTS.md` at the root of the project — plain Markdown, no required section: | |
| 8 | + | |
| 9 | + ```markdown | |
| 10 | + # Build and test | |
| 11 | + - Tests run with `make check`, never with `go test` directly. | |
| 12 | + | |
| 13 | + # Conventions | |
| 14 | + - Never edit files under `vendor/`. | |
| 15 | + - Commit messages start with the ticket number. | |
| 16 | + ``` | |
| 17 | + | |
| 18 | +2. Start mm **from that directory** (or open the project in your editor). The banner names the file and its size: | |
| 19 | + | |
| 20 | + ``` | |
| 21 | + mm … | yolo: on | agents.md: /home/me/project/AGENTS.md (~50 tokens) | |
| 22 | + ``` | |
| 23 | + | |
| 24 | + No configuration is needed: `agentsMd.enabled` is `true` by default. | |
| 25 | + | |
| 26 | +3. Ask as usual. The file is part of the system prompt of the conversation; the model follows it unless you ask otherwise in the chat. | |
| 27 | + | |
| 28 | +4. After editing `AGENTS.md`, type `/new` (or start a new session in the editor): the next conversation reads the new version. The current one keeps the version it started with. | |
| 29 | + | |
| 30 | +## Variants | |
| 31 | + | |
| 32 | +- **The file lives elsewhere in the project**: `agentsMd: {path: docs/AGENTS.md}`. A relative path is relative to the project directory, so one configuration serves every project. | |
| 33 | +- **Turn it off for one agent**: `agentsMd: {enabled: false}` in that agent's file. For an agent called by another one (`ask_agent`, `/ask`, `mm --ask`), only the called agent's own setting counts. | |
| 34 | +- **A long file on a small model**: the banner warns when the file takes 20 % of the context window or more. Shorten it, or set `agentsMd: {maxChars: 8000}` — above the limit, the file is not read at all (the banner says `skipped (too long)`); it is never cut in the middle. | |
| 35 | +- **In an editor (ACP)**: each session reads the `AGENTS.md` of the project it was opened in; the banner, printed before any session exists, shows `agents.md: <session cwd>/AGENTS.md`. | |
| 36 | +- **A JSON agent** (`structuredOutput`) never reads the file; the banner shows `agents.md: ignored (structured output)`. | |
| 37 | + | |
| 38 | +## Check what the model received | |
| 39 | + | |
| 40 | +Turn the trace on and read the system message of the first request: | |
| 41 | + | |
| 42 | +```bash | |
| 43 | +AGENT_TRACE=true mm agent.yaml -p "How do I run the tests here?" | |
| 44 | +jq -r '.messages[0].content' .mm/traces/*-0001.request.json | |
| 45 | +``` | |
| 46 | + | |
| 47 | +The file appears after the `system` prompt, under `PROJECT INSTRUCTIONS (from <path>)`. See [How to see the JSON sent to the model](inspect-model-traffic.md). | |
| 48 | + | |
| 49 | +## See also | |
| 50 | + | |
| 51 | +- [`agentsMd` reference](../reference/configuration.md#agentsmd-keys) — the keys, what is and is not read | |
| 52 | +- [Project instructions — explanation](../explanation/project-instructions.md) — why it joins the prompt this way, and when it is read | |
| new file mode 100644 | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | +# How to give the agent your project's instructions (`AGENTS.md`) | ||
| 2 | + | ||
| 3 | +This guide shows how to make mm follow the rules of the project it works in — how to build and test, what not to touch — without editing the agent's configuration. It assumes you already run `mm` in a project directory, or from an editor that opens one. | ||
| 4 | + | ||
| 5 | +## Steps | ||
| 6 | + | ||
| 7 | +1. Write an `AGENTS.md` at the root of the project — plain Markdown, no required section: | ||
| 8 | + | ||
| 9 | + ```markdown | ||
| 10 | + # Build and test | ||
| 11 | + - Tests run with `make check`, never with `go test` directly. | ||
| 12 | + | ||
| 13 | + # Conventions | ||
| 14 | + - Never edit files under `vendor/`. | ||
| 15 | + - Commit messages start with the ticket number. | ||
| 16 | + ``` | ||
| 17 | + | ||
| 18 | +2. Start mm **from that directory** (or open the project in your editor). The banner names the file and its size: | ||
| 19 | + | ||
| 20 | + ``` | ||
| 21 | + mm … | yolo: on | agents.md: /home/me/project/AGENTS.md (~50 tokens) | ||
| 22 | + ``` | ||
| 23 | + | ||
| 24 | + No configuration is needed: `agentsMd.enabled` is `true` by default. | ||
| 25 | + | ||
| 26 | +3. Ask as usual. The file is part of the system prompt of the conversation; the model follows it unless you ask otherwise in the chat. | ||
| 27 | + | ||
| 28 | +4. After editing `AGENTS.md`, type `/new` (or start a new session in the editor): the next conversation reads the new version. The current one keeps the version it started with. | ||
| 29 | + | ||
| 30 | +## Variants | ||
| 31 | + | ||
| 32 | +- **The file lives elsewhere in the project**: `agentsMd: {path: docs/AGENTS.md}`. A relative path is relative to the project directory, so one configuration serves every project. | ||
| 33 | +- **Turn it off for one agent**: `agentsMd: {enabled: false}` in that agent's file. For an agent called by another one (`ask_agent`, `/ask`, `mm --ask`), only the called agent's own setting counts. | ||
| 34 | +- **A long file on a small model**: the banner warns when the file takes 20 % of the context window or more. Shorten it, or set `agentsMd: {maxChars: 8000}` — above the limit, the file is not read at all (the banner says `skipped (too long)`); it is never cut in the middle. | ||
| 35 | +- **In an editor (ACP)**: each session reads the `AGENTS.md` of the project it was opened in; the banner, printed before any session exists, shows `agents.md: <session cwd>/AGENTS.md`. | ||
| 36 | +- **A JSON agent** (`structuredOutput`) never reads the file; the banner shows `agents.md: ignored (structured output)`. | ||
| 37 | + | ||
| 38 | +## Check what the model received | ||
| 39 | + | ||
| 40 | +Turn the trace on and read the system message of the first request: | ||
| 41 | + | ||
| 42 | +```bash | ||
| 43 | +AGENT_TRACE=true mm agent.yaml -p "How do I run the tests here?" | ||
| 44 | +jq -r '.messages[0].content' .mm/traces/*-0001.request.json | ||
| 45 | +``` | ||
| 46 | + | ||
| 47 | +The file appears after the `system` prompt, under `PROJECT INSTRUCTIONS (from <path>)`. See [How to see the JSON sent to the model](inspect-model-traffic.md). | ||
| 48 | + | ||
| 49 | +## See also | ||
| 50 | + | ||
| 51 | +- [`agentsMd` reference](../reference/configuration.md#agentsmd-keys) — the keys, what is and is not read | ||
| 52 | +- [Project instructions — explanation](../explanation/project-instructions.md) — why it joins the prompt this way, and when it is read | ||
modified
docs/en/reference/cli.md +1 -1 | @@ -74,7 +74,7 @@ Printed on stdout in terminal mode, on stderr in ACP mode. Dimmed when stdout is | ||
| 74 | 74 | | Line | Content | |
| 75 | 75 | |------|---------| |
| 76 | 76 | | `[warning: …]` | Zero or more probe warnings: server unreachable, model not in the server's list, no chat template, compression that can never trigger, no tool declared at all, an `mmConfigs` file not found (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), the deprecated `permissions` key. | |
| 77 | -| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| trace: …]` | Version (see above), configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found, `yolo: on` or `yolo: off (tools ask)`., and, while `trace.enabled` is on, `trace: <absolute dir>` (`trace: <session cwd>/<dir>` in ACP mode). | | |
| 77 | +| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| agents.md: …][ \| trace: …]` | Version (see above), configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found, `yolo: on` or `yolo: off (tools ask)`., and, while `trace.enabled` is on, `trace: <absolute dir>` (`trace: <session cwd>/<dir>` in ACP mode). `agents.md:` (with `agentsMd.enabled`): `<absolute path> (~N tokens)` when the project's file is read, `skipped (too long)` above `agentsMd.maxChars`, `ignored (structured output)` for an agent with `structuredOutput`, `<session cwd>/<path>` in ACP mode; absent when there is no file. A read file of 20 % of a known context window or more adds the warning `<path> is ~N tokens, X % of the W-token context window — every request carries it …`. | | |
| 78 | 78 | | `Agent ready. Commands: …` | Terminal mode only. | |
| 79 | 79 | | `Shortcut: Ctrl+C …` | Terminal mode only. | |
| 80 | 80 | | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Terminal mode, `sessions.enabled` only: the resolved sessions directory and how many sessions it already holds. | |
| @@ -74,7 +74,7 @@ Printed on stdout in terminal mode, on stderr in ACP mode. Dimmed when stdout is | |||
| 74 | | Line | Content | | 74 | | Line | Content | |
| 75 | |------|---------| | 75 | |------|---------| |
| 76 | | `[warning: …]` | Zero or more probe warnings: server unreachable, model not in the server's list, no chat template, compression that can never trigger, no tool declared at all, an `mmConfigs` file not found (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), the deprecated `permissions` key. | | 76 | | `[warning: …]` | Zero or more probe warnings: server unreachable, model not in the server's list, no chat template, compression that can never trigger, no tool declared at all, an `mmConfigs` file not found (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), the deprecated `permissions` key. | |
| 77 | -| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| trace: …]` | Version (see above), configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found, `yolo: on` or `yolo: off (tools ask)`., and, while `trace.enabled` is on, `trace: <absolute dir>` (`trace: <session cwd>/<dir>` in ACP mode). | | 77 | +| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| agents.md: …][ \| trace: …]` | Version (see above), configuration path, provider name, model identifier, context window with its origin (`config`, `/props`) or `unknown`, declared tool names, number of skills found, `yolo: on` or `yolo: off (tools ask)`., and, while `trace.enabled` is on, `trace: <absolute dir>` (`trace: <session cwd>/<dir>` in ACP mode). `agents.md:` (with `agentsMd.enabled`): `<absolute path> (~N tokens)` when the project's file is read, `skipped (too long)` above `agentsMd.maxChars`, `ignored (structured output)` for an agent with `structuredOutput`, `<session cwd>/<path>` in ACP mode; absent when there is no file. A read file of 20 % of a known context window or more adds the warning `<path> is ~N tokens, X % of the W-token context window — every request carries it …`. | |
| 78 | | `Agent ready. Commands: …` | Terminal mode only. | | 78 | | `Agent ready. Commands: …` | Terminal mode only. | |
| 79 | | `Shortcut: Ctrl+C …` | Terminal mode only. | | 79 | | `Shortcut: Ctrl+C …` | Terminal mode only. | |
| 80 | | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Terminal mode, `sessions.enabled` only: the resolved sessions directory and how many sessions it already holds. | | 80 | | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Terminal mode, `sessions.enabled` only: the resolved sessions directory and how many sessions it already holds. | |
modified
docs/en/reference/configuration.md +22 -0 | @@ -30,6 +30,7 @@ | ||
| 30 | 30 | | `context` | map | see below | Automatic history compression. | |
| 31 | 31 | | `sessions` | map | see below | Persistent, per-project sessions. | |
| 32 | 32 | | `trace` | map | see below | Write every request to the model and every response to files. | |
| 33 | +| `agentsMd` | map | see below | Append the project's `AGENTS.md` to the system prompt of every conversation. On by default. | | |
| 33 | 34 | | `acp` | map | see below | How `mm -acp` introduces itself to the editor. | |
| 34 | 35 | | `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. | |
| 35 | 36 | | `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. | |
| @@ -65,6 +66,25 @@ | ||
| 65 | 66 | |
| 66 | 67 | File names: `<YYYYMMDD-HHMMSS.mmm>-<acp.name>-<NNNN>.request.json`, then `….response-<status>.sse` (a streamed answer, the server's SSE lines byte for byte, written as they arrive), `….response-<status>.json` (a whole JSON body, indented), or `….response-error.txt` (no response: the method, the URL and the error). `NNNN` counts the model calls of the process; a retry by the HTTP client is a call of its own. An agent started by `/ask` or `ask_agent` inherits `AGENT_TRACE` and writes beside its asker, under its own `acp.name`. |
| 67 | 68 | |
| 69 | +## `agentsMd` keys | |
| 70 | + | |
| 71 | +| Key | Type | Default | Description | | |
| 72 | +|-----|------|---------|-------------| | |
| 73 | +| `agentsMd.enabled` | bool | `true` | Read the file at the start of every conversation: start-up, `/new`, `/resume`, a new ACP session (`session/new`), a reloaded one (`session/load`), `-p`. Never in the middle of a conversation. An agent started by `/ask`, `ask_agent` or `mm --ask` reads it only when its own file has it on; it looks in the working directory it is given (its asker's). | | |
| 74 | +| `agentsMd.path` | string | `AGENTS.md` | The file. A relative path is resolved against the conversation's working directory — the directory `mm` runs in, the session's `cwd` in ACP mode — not against the configuration file. An absolute path is used as-is. Must not be empty while `enabled` is `true`. | | |
| 75 | +| `agentsMd.maxChars` | int | `0` | Above this many characters (Unicode characters, after trimming) the file is not read at all, and the banner says so. Never truncated. `0` = no limit. Must be ≥ 0. | | |
| 76 | + | |
| 77 | +When the file is read, the system message is the `system` prompt, a blank line, then: | |
| 78 | + | |
| 79 | +```text | |
| 80 | +PROJECT INSTRUCTIONS (from <absolute path>) | |
| 81 | +The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins. | |
| 82 | + | |
| 83 | +<the file, trimmed> | |
| 84 | +``` | |
| 85 | + | |
| 86 | +Not read: a missing file, a file of blanks only, a file above `maxChars`, any file when `structuredOutput` is set, a file that cannot be read (banner warning `<path> cannot be read: …`). A resumed session (`/resume`, `session/load`) continues under the file as it is now. | |
| 87 | + | |
| 68 | 88 | ## `acp` keys |
| 69 | 89 | |
| 70 | 90 | | Key | Type | Default | Description | |
| @@ -173,6 +193,8 @@ Reported on stderr as `[config error: <path>: <message>]`, exit code 1 — in ev | ||
| 173 | 193 | | `context.maxMessages must be >= 0` | negative value | |
| 174 | 194 | | `sessions.dir must not be empty` | empty string | |
| 175 | 195 | | `trace.dir must not be empty` | empty string with the trace on | |
| 196 | +| `agentsMd.path must not be empty` | empty string with `agentsMd.enabled` on | | |
| 197 | +| `agentsMd.maxChars must be >= 0` | negative value | | |
| 176 | 198 | | `acp.name must not be empty` | empty or blank string | |
| 177 | 199 | | `lsp.servers[N].name must not be empty` | a server without `name` | |
| 178 | 200 | | `lsp.servers[N].command must not be empty` | a server without `command` | |
| @@ -30,6 +30,7 @@ | |||
| 30 | | `context` | map | see below | Automatic history compression. | | 30 | | `context` | map | see below | Automatic history compression. | |
| 31 | | `sessions` | map | see below | Persistent, per-project sessions. | | 31 | | `sessions` | map | see below | Persistent, per-project sessions. | |
| 32 | | `trace` | map | see below | Write every request to the model and every response to files. | | 32 | | `trace` | map | see below | Write every request to the model and every response to files. | |
| 33 | +| `agentsMd` | map | see below | Append the project's `AGENTS.md` to the system prompt of every conversation. On by default. | | ||
| 33 | | `acp` | map | see below | How `mm -acp` introduces itself to the editor. | | 34 | | `acp` | map | see below | How `mm -acp` introduces itself to the editor. | |
| 34 | | `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. | | 35 | | `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. | |
| 35 | | `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. | | 36 | | `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. | |
| @@ -65,6 +66,25 @@ | |||
| 65 | 66 | ||
| 66 | File names: `<YYYYMMDD-HHMMSS.mmm>-<acp.name>-<NNNN>.request.json`, then `….response-<status>.sse` (a streamed answer, the server's SSE lines byte for byte, written as they arrive), `….response-<status>.json` (a whole JSON body, indented), or `….response-error.txt` (no response: the method, the URL and the error). `NNNN` counts the model calls of the process; a retry by the HTTP client is a call of its own. An agent started by `/ask` or `ask_agent` inherits `AGENT_TRACE` and writes beside its asker, under its own `acp.name`. | 67 | File names: `<YYYYMMDD-HHMMSS.mmm>-<acp.name>-<NNNN>.request.json`, then `….response-<status>.sse` (a streamed answer, the server's SSE lines byte for byte, written as they arrive), `….response-<status>.json` (a whole JSON body, indented), or `….response-error.txt` (no response: the method, the URL and the error). `NNNN` counts the model calls of the process; a retry by the HTTP client is a call of its own. An agent started by `/ask` or `ask_agent` inherits `AGENT_TRACE` and writes beside its asker, under its own `acp.name`. |
| 67 | 68 | ||
| 69 | +## `agentsMd` keys | ||
| 70 | + | ||
| 71 | +| Key | Type | Default | Description | | ||
| 72 | +|-----|------|---------|-------------| | ||
| 73 | +| `agentsMd.enabled` | bool | `true` | Read the file at the start of every conversation: start-up, `/new`, `/resume`, a new ACP session (`session/new`), a reloaded one (`session/load`), `-p`. Never in the middle of a conversation. An agent started by `/ask`, `ask_agent` or `mm --ask` reads it only when its own file has it on; it looks in the working directory it is given (its asker's). | | ||
| 74 | +| `agentsMd.path` | string | `AGENTS.md` | The file. A relative path is resolved against the conversation's working directory — the directory `mm` runs in, the session's `cwd` in ACP mode — not against the configuration file. An absolute path is used as-is. Must not be empty while `enabled` is `true`. | | ||
| 75 | +| `agentsMd.maxChars` | int | `0` | Above this many characters (Unicode characters, after trimming) the file is not read at all, and the banner says so. Never truncated. `0` = no limit. Must be ≥ 0. | | ||
| 76 | + | ||
| 77 | +When the file is read, the system message is the `system` prompt, a blank line, then: | ||
| 78 | + | ||
| 79 | +```text | ||
| 80 | +PROJECT INSTRUCTIONS (from <absolute path>) | ||
| 81 | +The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins. | ||
| 82 | + | ||
| 83 | +<the file, trimmed> | ||
| 84 | +``` | ||
| 85 | + | ||
| 86 | +Not read: a missing file, a file of blanks only, a file above `maxChars`, any file when `structuredOutput` is set, a file that cannot be read (banner warning `<path> cannot be read: …`). A resumed session (`/resume`, `session/load`) continues under the file as it is now. | ||
| 87 | + | ||
| 68 | ## `acp` keys | 88 | ## `acp` keys |
| 69 | 89 | ||
| 70 | | Key | Type | Default | Description | | 90 | | Key | Type | Default | Description | |
| @@ -173,6 +193,8 @@ Reported on stderr as `[config error: <path>: <message>]`, exit code 1 — in ev | |||
| 173 | | `context.maxMessages must be >= 0` | negative value | | 193 | | `context.maxMessages must be >= 0` | negative value | |
| 174 | | `sessions.dir must not be empty` | empty string | | 194 | | `sessions.dir must not be empty` | empty string | |
| 175 | | `trace.dir must not be empty` | empty string with the trace on | | 195 | | `trace.dir must not be empty` | empty string with the trace on | |
| 196 | +| `agentsMd.path must not be empty` | empty string with `agentsMd.enabled` on | | ||
| 197 | +| `agentsMd.maxChars must be >= 0` | negative value | | ||
| 176 | | `acp.name must not be empty` | empty or blank string | | 198 | | `acp.name must not be empty` | empty or blank string | |
| 177 | | `lsp.servers[N].name must not be empty` | a server without `name` | | 199 | | `lsp.servers[N].name must not be empty` | a server without `name` | |
| 178 | | `lsp.servers[N].command must not be empty` | a server without `command` | | 200 | | `lsp.servers[N].command must not be empty` | a server without `command` | |
modified
docs/fr/README.md +2 -0 | @@ -15,6 +15,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | ||
| 15 | 15 | - [Faire tourner mini-me sur Ollama](how-to/run-with-ollama.md) — `provider: ollama`, la config de démo dans `demo-ollama/` |
| 16 | 16 | - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>` |
| 17 | 17 | - [Voir le JSON envoyé au modèle et sa réponse](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, lire les fichiers avec jq |
| 18 | +- [Donner à l'agent les consignes de votre projet](how-to/give-project-instructions.md) — `AGENTS.md`, lu à chaque conversation, réglages `agentsMd` | |
| 18 | 19 | - [Obtenir des réponses en JSON](how-to/get-json-answers.md) — `structuredOutput` : déclarations Go, `-p … | jq`, un agent JSON derrière un coordinateur |
| 19 | 20 | - [Laisser votre agent appeler d'autres agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, descriptions dans `mmConfigs`, `yolo` par agent |
| 20 | 21 | - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context` |
| @@ -64,6 +65,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | ||
| 64 | 65 | - [L'outil `fetch_content`](explanation/fetch-content-tool.md) — pourquoi un outil intégré à côté du `fetch` MCP, un convertisseur maison, le robots.txt, des erreurs numérotées en texte, les limites connues |
| 65 | 66 | - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie |
| 66 | 67 | - [Le mode yolo et les permissions](explanation/yolo-and-permissions.md) — pourquoi les outils s'exécutent sans demander par défaut, exister ou demander, quatre réponses dans un seul paquet, pourquoi l'agent qui interroge décide (et un `yolo` par agent dans `mmConfigs`) |
| 68 | +- [Consignes du projet (`AGENTS.md`)](explanation/project-instructions.md) — pourquoi ajoutées au prompt système, pourquoi par conversation, pourquoi un avertissement plutôt qu'une coupe, qui ne les lit pas | |
| 67 | 69 | - [Sortie structurée](explanation/structured-output.md) — pourquoi des déclarations Go, pourquoi aussi dans le prompt système, pourquoi pas d'outils, ce qui garantit la réponse |
| 68 | 70 | - [Interroger un autre agent](explanation/asking-another-agent.md) — pourquoi un autre agent mm est un processus enfant interrogé en ACP, la paire de messages, les sessions neuves, `ask_agent` et le `yolo` par agent, la limite de profondeur |
| 69 | 71 | - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) |
| @@ -15,6 +15,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | |||
| 15 | - [Faire tourner mini-me sur Ollama](how-to/run-with-ollama.md) — `provider: ollama`, la config de démo dans `demo-ollama/` | 15 | - [Faire tourner mini-me sur Ollama](how-to/run-with-ollama.md) — `provider: ollama`, la config de démo dans `demo-ollama/` |
| 16 | - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>` | 16 | - [Interroger un autre agent mm depuis votre session](how-to/ask-another-agent.md) — `mmConfigs`, `/ask-<nom>`, `/ask-with-context-<nom>` |
| 17 | - [Voir le JSON envoyé au modèle et sa réponse](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, lire les fichiers avec jq | 17 | - [Voir le JSON envoyé au modèle et sa réponse](how-to/inspect-model-traffic.md) — `trace`, `AGENT_TRACE`, lire les fichiers avec jq |
| 18 | +- [Donner à l'agent les consignes de votre projet](how-to/give-project-instructions.md) — `AGENTS.md`, lu à chaque conversation, réglages `agentsMd` | ||
| 18 | - [Obtenir des réponses en JSON](how-to/get-json-answers.md) — `structuredOutput` : déclarations Go, `-p … | jq`, un agent JSON derrière un coordinateur | 19 | - [Obtenir des réponses en JSON](how-to/get-json-answers.md) — `structuredOutput` : déclarations Go, `-p … | jq`, un agent JSON derrière un coordinateur |
| 19 | - [Laisser votre agent appeler d'autres agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, descriptions dans `mmConfigs`, `yolo` par agent | 20 | - [Laisser votre agent appeler d'autres agents](how-to/let-the-agent-call-other-agents.md) — `ask_agent`, descriptions dans `mmConfigs`, `yolo` par agent |
| 20 | - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context` | 21 | - [Interroger un autre agent depuis une skill ou un script](how-to/ask-another-agent-from-a-skill.md) — `"$MM_BIN" --ask <agent>`, `--ask-with-context` |
| @@ -64,6 +65,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | |||
| 64 | - [L'outil `fetch_content`](explanation/fetch-content-tool.md) — pourquoi un outil intégré à côté du `fetch` MCP, un convertisseur maison, le robots.txt, des erreurs numérotées en texte, les limites connues | 65 | - [L'outil `fetch_content`](explanation/fetch-content-tool.md) — pourquoi un outil intégré à côté du `fetch` MCP, un convertisseur maison, le robots.txt, des erreurs numérotées en texte, les limites connues |
| 65 | - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie | 66 | - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie |
| 66 | - [Le mode yolo et les permissions](explanation/yolo-and-permissions.md) — pourquoi les outils s'exécutent sans demander par défaut, exister ou demander, quatre réponses dans un seul paquet, pourquoi l'agent qui interroge décide (et un `yolo` par agent dans `mmConfigs`) | 67 | - [Le mode yolo et les permissions](explanation/yolo-and-permissions.md) — pourquoi les outils s'exécutent sans demander par défaut, exister ou demander, quatre réponses dans un seul paquet, pourquoi l'agent qui interroge décide (et un `yolo` par agent dans `mmConfigs`) |
| 68 | +- [Consignes du projet (`AGENTS.md`)](explanation/project-instructions.md) — pourquoi ajoutées au prompt système, pourquoi par conversation, pourquoi un avertissement plutôt qu'une coupe, qui ne les lit pas | ||
| 67 | - [Sortie structurée](explanation/structured-output.md) — pourquoi des déclarations Go, pourquoi aussi dans le prompt système, pourquoi pas d'outils, ce qui garantit la réponse | 69 | - [Sortie structurée](explanation/structured-output.md) — pourquoi des déclarations Go, pourquoi aussi dans le prompt système, pourquoi pas d'outils, ce qui garantit la réponse |
| 68 | - [Interroger un autre agent](explanation/asking-another-agent.md) — pourquoi un autre agent mm est un processus enfant interrogé en ACP, la paire de messages, les sessions neuves, `ask_agent` et le `yolo` par agent, la limite de profondeur | 70 | - [Interroger un autre agent](explanation/asking-another-agent.md) — pourquoi un autre agent mm est un processus enfant interrogé en ACP, la paire de messages, les sessions neuves, `ask_agent` et le `yolo` par agent, la limite de profondeur |
| 69 | - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) | 71 | - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) |
modified
docs/fr/explanation/architecture.md +1 -0 | @@ -9,6 +9,7 @@ Le point d'entrée, `main.go`, ne fait que du câblage : il charge les réglages | ||
| 9 | 9 | | Paquet | Responsabilité | |
| 10 | 10 | |--------|----------------| |
| 11 | 11 | | `config` | Tous les réglages dans une structure, avec des valeurs par défaut intégrées surchargées par le YAML et quelques variables d'environnement. | |
| 12 | +| `agentsmd` | Lit le `AGENTS.md` du projet dans le répertoire de travail d'une conversation et l'ajoute au prompt système (`System`) ; `Load` dit à la bannière ce qui a été trouvé. Appelé partout où une conversation commence : `agent`, `tui`, `acp`. Voir [Consignes du projet](project-instructions.md). | | |
| 12 | 13 | | `structured` | Traduit les déclarations Go de `structuredOutput` en JSON Schema que doit suivre la réponse, avec `go/parser` ; utilisé par `config` au chargement du fichier. Voir [Sortie structurée](structured-output.md). | |
| 13 | 14 | | `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, le registre des fournisseurs, le raisonnement qu'un modèle diffuse à côté de sa réponse (voir [Modèles à raisonnement](thinking-models.md)), et la trace optionnelle de chaque requête et réponse (`trace.go`). | |
| 14 | 15 | | `tools` | Les outils intégrés que le modèle peut appeler, `ask_agent` compris (il interroge un agent de `mmConfigs` via `ask`). | |
| @@ -9,6 +9,7 @@ Le point d'entrée, `main.go`, ne fait que du câblage : il charge les réglages | |||
| 9 | | Paquet | Responsabilité | | 9 | | Paquet | Responsabilité | |
| 10 | |--------|----------------| | 10 | |--------|----------------| |
| 11 | | `config` | Tous les réglages dans une structure, avec des valeurs par défaut intégrées surchargées par le YAML et quelques variables d'environnement. | | 11 | | `config` | Tous les réglages dans une structure, avec des valeurs par défaut intégrées surchargées par le YAML et quelques variables d'environnement. | |
| 12 | +| `agentsmd` | Lit le `AGENTS.md` du projet dans le répertoire de travail d'une conversation et l'ajoute au prompt système (`System`) ; `Load` dit à la bannière ce qui a été trouvé. Appelé partout où une conversation commence : `agent`, `tui`, `acp`. Voir [Consignes du projet](project-instructions.md). | | ||
| 12 | | `structured` | Traduit les déclarations Go de `structuredOutput` en JSON Schema que doit suivre la réponse, avec `go/parser` ; utilisé par `config` au chargement du fichier. Voir [Sortie structurée](structured-output.md). | | 13 | | `structured` | Traduit les déclarations Go de `structuredOutput` en JSON Schema que doit suivre la réponse, avec `go/parser` ; utilisé par `config` au chargement du fichier. Voir [Sortie structurée](structured-output.md). | |
| 13 | | `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, le registre des fournisseurs, le raisonnement qu'un modèle diffuse à côté de sa réponse (voir [Modèles à raisonnement](thinking-models.md)), et la trace optionnelle de chaque requête et réponse (`trace.go`). | | 14 | | `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, le registre des fournisseurs, le raisonnement qu'un modèle diffuse à côté de sa réponse (voir [Modèles à raisonnement](thinking-models.md)), et la trace optionnelle de chaque requête et réponse (`trace.go`). | |
| 14 | | `tools` | Les outils intégrés que le modèle peut appeler, `ask_agent` compris (il interroge un agent de `mmConfigs` via `ask`). | | 15 | | `tools` | Les outils intégrés que le modèle peut appeler, `ask_agent` compris (il interroge un agent de `mmConfigs` via `ask`). | |
added
docs/fr/explanation/project-instructions.md +38 -0 | new file mode 100644 | ||
| @@ -0,0 +1,38 @@ | ||
| 1 | +# Consignes du projet (`AGENTS.md`) — explication | |
| 2 | + | |
| 3 | +## De quoi s'agit-il ? | |
| 4 | + | |
| 5 | +La configuration d'un agent dit ce qu'est l'agent : son nom, ses outils, sa façon de modifier les fichiers. Elle ne peut pas dire comment chaque projet où il travaille se compile, se teste ou s'organise — un même `agent.yaml` sert pour beaucoup de projets. `AGENTS.md` (https://agents.md) est la convention que partagent les agents de code pour cela : un simple fichier Markdown à la racine d'un dépôt, écrit pour les agents. mm le lit dans le répertoire où travaille une conversation et l'ajoute au prompt système (ticket 81). | |
| 6 | + | |
| 7 | +## Pourquoi il est ajouté au prompt système | |
| 8 | + | |
| 9 | +Le fichier suit le prompt `system` de la configuration, sous un titre qui nomme son origine — `PROJECT INSTRUCTIONS (from <chemin>)` — et ne le remplace jamais : les deux répondent à des questions différentes, et l'agent a besoin des deux. Le titre indique au modèle que ces règles viennent du projet, pas de son rôle, ce qui l'aide quand elles divergent (un agent à qui l'on demande de répondre en anglais, un projet dont les commentaires sont en français). | |
| 10 | + | |
| 11 | +Un premier message utilisateur (« voici les consignes du projet ») a été écarté : les petits modèles locaux accordent plus de poids au message système, et certains templates de chat (Gemma, Mistral) refusent deux tours utilisateur d'affilée. Le même mécanisme porte déjà le paragraphe `OUTPUT FORMAT` de `structuredOutput`. | |
| 12 | + | |
| 13 | +## Pourquoi il est lu par conversation, pas par question | |
| 14 | + | |
| 15 | +Le fichier est lu quand une conversation commence — au démarrage, sur `/new`, sur `/resume`, pour une session d'éditeur nouvelle ou rechargée — et plus jusqu'à la suivante. Le relire avant chaque question prendrait une modification plus tôt en compte, mais un message système qui change en cours de conversation oblige le serveur à relire tout l'historique : llama.cpp garde en cache le début du prompt déjà traité, et le message système en est la première partie (le même cache que le ticket 53 protège en gardant les outils dans un ordre stable). Sur une longue conversation locale, ce sont des minutes par question. Une version par conversation garde le cache ; `/new` est le moyen de prendre une modification en compte. | |
| 16 | + | |
| 17 | +`/resume` suit la règle que mm appliquait déjà au prompt `system` : une conversation reprise continue sous le prompt actuel de l'agent, elle reçoit donc aussi le fichier actuel. Une session reprise est de toute façon relue en entier par le serveur, cela ne coûte rien. | |
| 18 | + | |
| 19 | +## Pourquoi le répertoire de travail, et lui seul | |
| 20 | + | |
| 21 | +Le répertoire de travail, c'est le projet : le répertoire où tourne `mm`, ou le `cwd` qu'un éditeur donne à chaque session. Chaque session ACP lit donc le fichier de son propre projet, et un `agentsMd.path` relatif est résolu là, jamais à côté du fichier de configuration — sinon un même `agent.yaml` lirait le même fichier dans tous les projets. | |
| 22 | + | |
| 23 | +La convention autorise aussi des fichiers imbriqués dans un monorepo, le plus proche l'emportant. mm ne remonte pas jusqu'à la racine du dépôt et ne concatène pas de fichiers : un fichier, un endroit, un comportement prévisible. | |
| 24 | + | |
| 25 | +## Pourquoi un avertissement plutôt qu'une coupe | |
| 26 | + | |
| 27 | +Le message système part avec chaque requête et occupe la fenêtre de contexte pendant toute la conversation. Quelques pages d'`AGENTS.md` ne sont rien pour un modèle cloud, et la moitié de la fenêtre d'un modèle 4B servi avec 16k tokens. mm le mesure — la même estimation que la compression du contexte, les caractères divisés par 3,5 — et avertit à partir de 20 % d'une fenêtre connue. Il ne le coupe pas : une troncature garde le début et laisse tomber la fin en silence, qui peut contenir la seule règle qui comptait, pendant que le modèle croit avoir tout lu. `agentsMd.maxChars` existe pour qui veut une limite ferme ; au-delà, le fichier est laissé de côté en entier et la bannière le dit — un fichier absent est un état que l'utilisateur voit, un fichier à moitié lu ne l'est pas. | |
| 28 | + | |
| 29 | +## Les agents qui ne le lisent pas | |
| 30 | + | |
| 31 | +- **Les agents JSON** (`structuredOutput`) : des consignes en prose (« termine chaque réponse par une signature ») contrediraient la contrainte JSON, et une signature ajoutée au JSON casserait tout script qui le lit. La bannière mentionne le fichier ignoré de façon neutre, puisque c'est voulu. | |
| 32 | +- **Les agents appelés par un autre** (`/ask`, `ask_agent`, `mm --ask`) : ils tournent comme leur propre processus `mm` avec leur propre configuration, et ne lisent le fichier du répertoire de travail qu'on leur donne que si leur propre `agentsMd.enabled` le dit. Un relecteur sans outils peut le désactiver ; un assistant de code le garde et suit les mêmes règles que l'agent qui l'a appelé. | |
| 33 | + | |
| 34 | +## Liens avec le reste | |
| 35 | + | |
| 36 | +- [Sortie structurée](structured-output.md) — l'autre paragraphe que mm ajoute au prompt système. | |
| 37 | +- [Interroger un autre agent](asking-another-agent.md) — le répertoire de travail que reçoit un agent interrogé. | |
| 38 | +- [Comment donner à l'agent les consignes de votre projet](../how-to/give-project-instructions.md) et la [référence de `agentsMd`](../reference/configuration.md#clés-de-agentsmd). | |
| new file mode 100644 | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | +# Consignes du projet (`AGENTS.md`) — explication | ||
| 2 | + | ||
| 3 | +## De quoi s'agit-il ? | ||
| 4 | + | ||
| 5 | +La configuration d'un agent dit ce qu'est l'agent : son nom, ses outils, sa façon de modifier les fichiers. Elle ne peut pas dire comment chaque projet où il travaille se compile, se teste ou s'organise — un même `agent.yaml` sert pour beaucoup de projets. `AGENTS.md` (https://agents.md) est la convention que partagent les agents de code pour cela : un simple fichier Markdown à la racine d'un dépôt, écrit pour les agents. mm le lit dans le répertoire où travaille une conversation et l'ajoute au prompt système (ticket 81). | ||
| 6 | + | ||
| 7 | +## Pourquoi il est ajouté au prompt système | ||
| 8 | + | ||
| 9 | +Le fichier suit le prompt `system` de la configuration, sous un titre qui nomme son origine — `PROJECT INSTRUCTIONS (from <chemin>)` — et ne le remplace jamais : les deux répondent à des questions différentes, et l'agent a besoin des deux. Le titre indique au modèle que ces règles viennent du projet, pas de son rôle, ce qui l'aide quand elles divergent (un agent à qui l'on demande de répondre en anglais, un projet dont les commentaires sont en français). | ||
| 10 | + | ||
| 11 | +Un premier message utilisateur (« voici les consignes du projet ») a été écarté : les petits modèles locaux accordent plus de poids au message système, et certains templates de chat (Gemma, Mistral) refusent deux tours utilisateur d'affilée. Le même mécanisme porte déjà le paragraphe `OUTPUT FORMAT` de `structuredOutput`. | ||
| 12 | + | ||
| 13 | +## Pourquoi il est lu par conversation, pas par question | ||
| 14 | + | ||
| 15 | +Le fichier est lu quand une conversation commence — au démarrage, sur `/new`, sur `/resume`, pour une session d'éditeur nouvelle ou rechargée — et plus jusqu'à la suivante. Le relire avant chaque question prendrait une modification plus tôt en compte, mais un message système qui change en cours de conversation oblige le serveur à relire tout l'historique : llama.cpp garde en cache le début du prompt déjà traité, et le message système en est la première partie (le même cache que le ticket 53 protège en gardant les outils dans un ordre stable). Sur une longue conversation locale, ce sont des minutes par question. Une version par conversation garde le cache ; `/new` est le moyen de prendre une modification en compte. | ||
| 16 | + | ||
| 17 | +`/resume` suit la règle que mm appliquait déjà au prompt `system` : une conversation reprise continue sous le prompt actuel de l'agent, elle reçoit donc aussi le fichier actuel. Une session reprise est de toute façon relue en entier par le serveur, cela ne coûte rien. | ||
| 18 | + | ||
| 19 | +## Pourquoi le répertoire de travail, et lui seul | ||
| 20 | + | ||
| 21 | +Le répertoire de travail, c'est le projet : le répertoire où tourne `mm`, ou le `cwd` qu'un éditeur donne à chaque session. Chaque session ACP lit donc le fichier de son propre projet, et un `agentsMd.path` relatif est résolu là, jamais à côté du fichier de configuration — sinon un même `agent.yaml` lirait le même fichier dans tous les projets. | ||
| 22 | + | ||
| 23 | +La convention autorise aussi des fichiers imbriqués dans un monorepo, le plus proche l'emportant. mm ne remonte pas jusqu'à la racine du dépôt et ne concatène pas de fichiers : un fichier, un endroit, un comportement prévisible. | ||
| 24 | + | ||
| 25 | +## Pourquoi un avertissement plutôt qu'une coupe | ||
| 26 | + | ||
| 27 | +Le message système part avec chaque requête et occupe la fenêtre de contexte pendant toute la conversation. Quelques pages d'`AGENTS.md` ne sont rien pour un modèle cloud, et la moitié de la fenêtre d'un modèle 4B servi avec 16k tokens. mm le mesure — la même estimation que la compression du contexte, les caractères divisés par 3,5 — et avertit à partir de 20 % d'une fenêtre connue. Il ne le coupe pas : une troncature garde le début et laisse tomber la fin en silence, qui peut contenir la seule règle qui comptait, pendant que le modèle croit avoir tout lu. `agentsMd.maxChars` existe pour qui veut une limite ferme ; au-delà, le fichier est laissé de côté en entier et la bannière le dit — un fichier absent est un état que l'utilisateur voit, un fichier à moitié lu ne l'est pas. | ||
| 28 | + | ||
| 29 | +## Les agents qui ne le lisent pas | ||
| 30 | + | ||
| 31 | +- **Les agents JSON** (`structuredOutput`) : des consignes en prose (« termine chaque réponse par une signature ») contrediraient la contrainte JSON, et une signature ajoutée au JSON casserait tout script qui le lit. La bannière mentionne le fichier ignoré de façon neutre, puisque c'est voulu. | ||
| 32 | +- **Les agents appelés par un autre** (`/ask`, `ask_agent`, `mm --ask`) : ils tournent comme leur propre processus `mm` avec leur propre configuration, et ne lisent le fichier du répertoire de travail qu'on leur donne que si leur propre `agentsMd.enabled` le dit. Un relecteur sans outils peut le désactiver ; un assistant de code le garde et suit les mêmes règles que l'agent qui l'a appelé. | ||
| 33 | + | ||
| 34 | +## Liens avec le reste | ||
| 35 | + | ||
| 36 | +- [Sortie structurée](structured-output.md) — l'autre paragraphe que mm ajoute au prompt système. | ||
| 37 | +- [Interroger un autre agent](asking-another-agent.md) — le répertoire de travail que reçoit un agent interrogé. | ||
| 38 | +- [Comment donner à l'agent les consignes de votre projet](../how-to/give-project-instructions.md) et la [référence de `agentsMd`](../reference/configuration.md#clés-de-agentsmd). | ||
added
docs/fr/how-to/give-project-instructions.md +52 -0 | new file mode 100644 | ||
| @@ -0,0 +1,52 @@ | ||
| 1 | +# Comment donner à l'agent les consignes de votre projet (`AGENTS.md`) | |
| 2 | + | |
| 3 | +Ce guide montre comment faire suivre à mm les règles du projet dans lequel il travaille — comment compiler et tester, ce qu'il ne faut pas toucher — sans modifier la configuration de l'agent. Il suppose que vous lancez déjà `mm` dans le dossier d'un projet, ou depuis un éditeur qui en ouvre un. | |
| 4 | + | |
| 5 | +## Étapes | |
| 6 | + | |
| 7 | +1. Écrivez un `AGENTS.md` à la racine du projet — du Markdown simple, sans section obligatoire : | |
| 8 | + | |
| 9 | + ```markdown | |
| 10 | + # Build and test | |
| 11 | + - Tests run with `make check`, never with `go test` directly. | |
| 12 | + | |
| 13 | + # Conventions | |
| 14 | + - Never edit files under `vendor/`. | |
| 15 | + - Commit messages start with the ticket number. | |
| 16 | + ``` | |
| 17 | + | |
| 18 | +2. Lancez mm **depuis ce dossier** (ou ouvrez le projet dans votre éditeur). La bannière nomme le fichier et sa taille : | |
| 19 | + | |
| 20 | + ``` | |
| 21 | + mm … | yolo: on | agents.md: /home/me/project/AGENTS.md (~50 tokens) | |
| 22 | + ``` | |
| 23 | + | |
| 24 | + Aucune configuration n'est nécessaire : `agentsMd.enabled` vaut `true` par défaut. | |
| 25 | + | |
| 26 | +3. Posez vos questions comme d'habitude. Le fichier fait partie du prompt système de la conversation ; le modèle le suit, sauf si vous demandez autre chose dans le chat. | |
| 27 | + | |
| 28 | +4. Après avoir modifié `AGENTS.md`, tapez `/new` (ou ouvrez une nouvelle session dans l'éditeur) : la conversation suivante lit la nouvelle version. La conversation en cours garde la version avec laquelle elle a commencé. | |
| 29 | + | |
| 30 | +## Variantes | |
| 31 | + | |
| 32 | +- **Le fichier est ailleurs dans le projet** : `agentsMd: {path: docs/AGENTS.md}`. Un chemin relatif l'est au dossier du projet, si bien qu'une même configuration sert pour tous les projets. | |
| 33 | +- **Le désactiver pour un agent** : `agentsMd: {enabled: false}` dans le fichier de cet agent. Pour un agent appelé par un autre (`ask_agent`, `/ask`, `mm --ask`), seul compte le réglage de l'agent appelé. | |
| 34 | +- **Un long fichier sur un petit modèle** : la bannière avertit quand le fichier occupe 20 % ou plus de la fenêtre de contexte. Raccourcissez-le, ou fixez `agentsMd: {maxChars: 8000}` — au-delà de la limite, le fichier n'est pas lu du tout (la bannière indique `skipped (too long)`) ; il n'est jamais coupé au milieu. | |
| 35 | +- **Dans un éditeur (ACP)** : chaque session lit le `AGENTS.md` du projet dans lequel elle a été ouverte ; la bannière, affichée avant toute session, indique `agents.md: <session cwd>/AGENTS.md`. | |
| 36 | +- **Un agent JSON** (`structuredOutput`) ne lit jamais le fichier ; la bannière indique `agents.md: ignored (structured output)`. | |
| 37 | + | |
| 38 | +## Vérifier ce que le modèle a reçu | |
| 39 | + | |
| 40 | +Activez la trace et lisez le message système de la première requête : | |
| 41 | + | |
| 42 | +```bash | |
| 43 | +AGENT_TRACE=true mm agent.yaml -p "Comment lance-t-on les tests ici ?" | |
| 44 | +jq -r '.messages[0].content' .mm/traces/*-0001.request.json | |
| 45 | +``` | |
| 46 | + | |
| 47 | +Le fichier apparaît après le prompt `system`, sous `PROJECT INSTRUCTIONS (from <chemin>)`. Voir [Voir le JSON envoyé au modèle](inspect-model-traffic.md). | |
| 48 | + | |
| 49 | +## Voir aussi | |
| 50 | + | |
| 51 | +- [Référence de `agentsMd`](../reference/configuration.md#clés-de-agentsmd) — les clés, ce qui est lu et ce qui ne l'est pas | |
| 52 | +- [Consignes du projet — explication](../explanation/project-instructions.md) — pourquoi elles rejoignent le prompt de cette façon, et quand elles sont lues | |
| new file mode 100644 | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | +# Comment donner à l'agent les consignes de votre projet (`AGENTS.md`) | ||
| 2 | + | ||
| 3 | +Ce guide montre comment faire suivre à mm les règles du projet dans lequel il travaille — comment compiler et tester, ce qu'il ne faut pas toucher — sans modifier la configuration de l'agent. Il suppose que vous lancez déjà `mm` dans le dossier d'un projet, ou depuis un éditeur qui en ouvre un. | ||
| 4 | + | ||
| 5 | +## Étapes | ||
| 6 | + | ||
| 7 | +1. Écrivez un `AGENTS.md` à la racine du projet — du Markdown simple, sans section obligatoire : | ||
| 8 | + | ||
| 9 | + ```markdown | ||
| 10 | + # Build and test | ||
| 11 | + - Tests run with `make check`, never with `go test` directly. | ||
| 12 | + | ||
| 13 | + # Conventions | ||
| 14 | + - Never edit files under `vendor/`. | ||
| 15 | + - Commit messages start with the ticket number. | ||
| 16 | + ``` | ||
| 17 | + | ||
| 18 | +2. Lancez mm **depuis ce dossier** (ou ouvrez le projet dans votre éditeur). La bannière nomme le fichier et sa taille : | ||
| 19 | + | ||
| 20 | + ``` | ||
| 21 | + mm … | yolo: on | agents.md: /home/me/project/AGENTS.md (~50 tokens) | ||
| 22 | + ``` | ||
| 23 | + | ||
| 24 | + Aucune configuration n'est nécessaire : `agentsMd.enabled` vaut `true` par défaut. | ||
| 25 | + | ||
| 26 | +3. Posez vos questions comme d'habitude. Le fichier fait partie du prompt système de la conversation ; le modèle le suit, sauf si vous demandez autre chose dans le chat. | ||
| 27 | + | ||
| 28 | +4. Après avoir modifié `AGENTS.md`, tapez `/new` (ou ouvrez une nouvelle session dans l'éditeur) : la conversation suivante lit la nouvelle version. La conversation en cours garde la version avec laquelle elle a commencé. | ||
| 29 | + | ||
| 30 | +## Variantes | ||
| 31 | + | ||
| 32 | +- **Le fichier est ailleurs dans le projet** : `agentsMd: {path: docs/AGENTS.md}`. Un chemin relatif l'est au dossier du projet, si bien qu'une même configuration sert pour tous les projets. | ||
| 33 | +- **Le désactiver pour un agent** : `agentsMd: {enabled: false}` dans le fichier de cet agent. Pour un agent appelé par un autre (`ask_agent`, `/ask`, `mm --ask`), seul compte le réglage de l'agent appelé. | ||
| 34 | +- **Un long fichier sur un petit modèle** : la bannière avertit quand le fichier occupe 20 % ou plus de la fenêtre de contexte. Raccourcissez-le, ou fixez `agentsMd: {maxChars: 8000}` — au-delà de la limite, le fichier n'est pas lu du tout (la bannière indique `skipped (too long)`) ; il n'est jamais coupé au milieu. | ||
| 35 | +- **Dans un éditeur (ACP)** : chaque session lit le `AGENTS.md` du projet dans lequel elle a été ouverte ; la bannière, affichée avant toute session, indique `agents.md: <session cwd>/AGENTS.md`. | ||
| 36 | +- **Un agent JSON** (`structuredOutput`) ne lit jamais le fichier ; la bannière indique `agents.md: ignored (structured output)`. | ||
| 37 | + | ||
| 38 | +## Vérifier ce que le modèle a reçu | ||
| 39 | + | ||
| 40 | +Activez la trace et lisez le message système de la première requête : | ||
| 41 | + | ||
| 42 | +```bash | ||
| 43 | +AGENT_TRACE=true mm agent.yaml -p "Comment lance-t-on les tests ici ?" | ||
| 44 | +jq -r '.messages[0].content' .mm/traces/*-0001.request.json | ||
| 45 | +``` | ||
| 46 | + | ||
| 47 | +Le fichier apparaît après le prompt `system`, sous `PROJECT INSTRUCTIONS (from <chemin>)`. Voir [Voir le JSON envoyé au modèle](inspect-model-traffic.md). | ||
| 48 | + | ||
| 49 | +## Voir aussi | ||
| 50 | + | ||
| 51 | +- [Référence de `agentsMd`](../reference/configuration.md#clés-de-agentsmd) — les clés, ce qui est lu et ce qui ne l'est pas | ||
| 52 | +- [Consignes du projet — explication](../explanation/project-instructions.md) — pourquoi elles rejoignent le prompt de cette façon, et quand elles sont lues | ||
modified
docs/fr/reference/cli.md +1 -1 | @@ -74,7 +74,7 @@ Affichée sur stdout en mode terminal, sur stderr en mode ACP. Grisée quand std | ||
| 74 | 74 | | Ligne | Contenu | |
| 75 | 75 | |-------|---------| |
| 76 | 76 | | `[warning: …]` | Zéro ou plusieurs avertissements de la sonde : serveur injoignable, modèle absent de la liste du serveur, pas de template de chat, compression qui ne peut jamais se déclencher, aucun outil déclaré, un fichier de `mmConfigs` introuvable (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), la clé obsolète `permissions`. | |
| 77 | -| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| trace: …]` | Version (voir ci-dessus), chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés, `yolo: on` ou `yolo: off (tools ask)`., et, tant que `trace.enabled` est actif, `trace: <répertoire absolu>` (`trace: <session cwd>/<dir>` en mode ACP). | | |
| 77 | +| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| agents.md: …][ \| trace: …]` | Version (voir ci-dessus), chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés, `yolo: on` ou `yolo: off (tools ask)`., et, tant que `trace.enabled` est actif, `trace: <répertoire absolu>` (`trace: <session cwd>/<dir>` en mode ACP). `agents.md:` (avec `agentsMd.enabled`) : `<chemin absolu> (~N tokens)` quand le fichier du projet est lu, `skipped (too long)` au-delà de `agentsMd.maxChars`, `ignored (structured output)` pour un agent avec `structuredOutput`, `<session cwd>/<chemin>` en mode ACP ; absent quand il n'y a pas de fichier. Un fichier lu qui occupe 20 % ou plus d'une fenêtre de contexte connue ajoute l'avertissement `<chemin> is ~N tokens, X % of the W-token context window — every request carries it …`. | | |
| 78 | 78 | | `Agent ready. Commands: …` | Mode terminal seulement. | |
| 79 | 79 | | `Shortcut: Ctrl+C …` | Mode terminal seulement. | |
| 80 | 80 | | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Mode terminal, avec `sessions.enabled` seulement : le répertoire de sessions résolu et le nombre de sessions qu'il contient déjà. | |
| @@ -74,7 +74,7 @@ Affichée sur stdout en mode terminal, sur stderr en mode ACP. Grisée quand std | |||
| 74 | | Ligne | Contenu | | 74 | | Ligne | Contenu | |
| 75 | |-------|---------| | 75 | |-------|---------| |
| 76 | | `[warning: …]` | Zéro ou plusieurs avertissements de la sonde : serveur injoignable, modèle absent de la liste du serveur, pas de template de chat, compression qui ne peut jamais se déclencher, aucun outil déclaré, un fichier de `mmConfigs` introuvable (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), la clé obsolète `permissions`. | | 76 | | `[warning: …]` | Zéro ou plusieurs avertissements de la sonde : serveur injoignable, modèle absent de la liste du serveur, pas de template de chat, compression qui ne peut jamais se déclencher, aucun outil déclaré, un fichier de `mmConfigs` introuvable (`mmConfigs: <name> → <path> not found — /ask-<name> will fail`), la clé obsolète `permissions`. | |
| 77 | -| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| trace: …]` | Version (voir ci-dessus), chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés, `yolo: on` ou `yolo: off (tools ask)`., et, tant que `trace.enabled` est actif, `trace: <répertoire absolu>` (`trace: <session cwd>/<dir>` en mode ACP). | | 77 | +| `mm <version> \| config: … \| provider: … \| model: … \| ctx: … \| tools: … \| skills: N \| yolo: …[ \| agents.md: …][ \| trace: …]` | Version (voir ci-dessus), chemin de la configuration, nom du fournisseur, identifiant du modèle, fenêtre de contexte avec son origine (`config`, `/props`) ou `unknown`, noms des outils déclarés, nombre de skills trouvés, `yolo: on` ou `yolo: off (tools ask)`., et, tant que `trace.enabled` est actif, `trace: <répertoire absolu>` (`trace: <session cwd>/<dir>` en mode ACP). `agents.md:` (avec `agentsMd.enabled`) : `<chemin absolu> (~N tokens)` quand le fichier du projet est lu, `skipped (too long)` au-delà de `agentsMd.maxChars`, `ignored (structured output)` pour un agent avec `structuredOutput`, `<session cwd>/<chemin>` en mode ACP ; absent quand il n'y a pas de fichier. Un fichier lu qui occupe 20 % ou plus d'une fenêtre de contexte connue ajoute l'avertissement `<chemin> is ~N tokens, X % of the W-token context window — every request carries it …`. | |
| 78 | | `Agent ready. Commands: …` | Mode terminal seulement. | | 78 | | `Agent ready. Commands: …` | Mode terminal seulement. | |
| 79 | | `Shortcut: Ctrl+C …` | Mode terminal seulement. | | 79 | | `Shortcut: Ctrl+C …` | Mode terminal seulement. | |
| 80 | | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Mode terminal, avec `sessions.enabled` seulement : le répertoire de sessions résolu et le nombre de sessions qu'il contient déjà. | | 80 | | `sessions: saved under <dir>[ · N earlier — /resume to continue the latest, /sessions to list]` | Mode terminal, avec `sessions.enabled` seulement : le répertoire de sessions résolu et le nombre de sessions qu'il contient déjà. | |
modified
docs/fr/reference/configuration.md +22 -0 | @@ -29,6 +29,7 @@ | ||
| 29 | 29 | | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. | |
| 30 | 30 | | `context` | dictionnaire | voir ci-dessous | Compression automatique de l'historique. | |
| 31 | 31 | | `sessions` | dictionnaire | voir ci-dessous | Sessions persistantes, par projet. | |
| 32 | +| `agentsMd` | dictionnaire | voir ci-dessous | Ajoute le `AGENTS.md` du projet au prompt système de chaque conversation. Actif par défaut. | | |
| 32 | 33 | | `trace` | dictionnaire | voir ci-dessous | Écrit chaque requête au modèle et chaque réponse dans des fichiers. | |
| 33 | 34 | | `acp` | dictionnaire | voir ci-dessous | La façon dont `mm -acp` se présente à l'éditeur. | |
| 34 | 35 | | `lsp` | dictionnaire | voir ci-dessous | Serveurs de langage derrière l'outil `lsp`. Aucun serveur listé = pas d'outil. | |
| @@ -65,6 +66,25 @@ | ||
| 65 | 66 | |
| 66 | 67 | Noms des fichiers : `<AAAAMMJJ-HHMMSS.mmm>-<acp.name>-<NNNN>.request.json`, puis `….response-<statut>.sse` (une réponse en streaming, les lignes SSE du serveur octet pour octet, écrites à leur arrivée), `….response-<statut>.json` (un corps JSON entier, indenté), ou `….response-error.txt` (pas de réponse : la méthode, l'URL et l'erreur). `NNNN` compte les appels au modèle du processus ; une nouvelle tentative du client HTTP est un appel à part. Un agent lancé par `/ask` ou `ask_agent` hérite de `AGENT_TRACE` et écrit à côté de celui qui l'interroge, sous son propre `acp.name`. |
| 67 | 68 | |
| 69 | +## Clés de `agentsMd` | |
| 70 | + | |
| 71 | +| Clé | Type | Défaut | Description | | |
| 72 | +|-----|------|--------|-------------| | |
| 73 | +| `agentsMd.enabled` | booléen | `true` | Lit le fichier au début de chaque conversation : démarrage, `/new`, `/resume`, une nouvelle session ACP (`session/new`), une session rechargée (`session/load`), `-p`. Jamais au milieu d'une conversation. Un agent démarré par `/ask`, `ask_agent` ou `mm --ask` ne le lit que si son propre fichier l'active ; il cherche dans le répertoire de travail qu'on lui donne (celui de l'agent qui l'interroge). | | |
| 74 | +| `agentsMd.path` | chaîne | `AGENTS.md` | Le fichier. Un chemin relatif est résolu par rapport au répertoire de travail de la conversation — le répertoire où tourne `mm`, le `cwd` de la session en mode ACP — et non par rapport au fichier de configuration. Un chemin absolu est utilisé tel quel. Ne doit pas être vide quand `enabled` vaut `true`. | | |
| 75 | +| `agentsMd.maxChars` | entier | `0` | Au-delà de ce nombre de caractères (caractères Unicode, après suppression des blancs aux extrémités), le fichier n'est pas lu du tout, et la bannière le dit. Jamais tronqué. `0` = pas de limite. Doit être ≥ 0. | | |
| 76 | + | |
| 77 | +Quand le fichier est lu, le message système est le prompt `system`, une ligne vide, puis : | |
| 78 | + | |
| 79 | +```text | |
| 80 | +PROJECT INSTRUCTIONS (from <chemin absolu>) | |
| 81 | +The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins. | |
| 82 | + | |
| 83 | +<le fichier, sans blancs aux extrémités> | |
| 84 | +``` | |
| 85 | + | |
| 86 | +Non lu : un fichier absent, un fichier qui ne contient que des blancs, un fichier au-delà de `maxChars`, tout fichier quand `structuredOutput` est défini, un fichier illisible (avertissement de bannière `<chemin> cannot be read: …`). Une session reprise (`/resume`, `session/load`) continue sous le fichier tel qu'il est maintenant. | |
| 87 | + | |
| 68 | 88 | ## Clés de `acp` |
| 69 | 89 | |
| 70 | 90 | | Clé | Type | Défaut | Description | |
| @@ -172,6 +192,8 @@ Signalées sur stderr sous la forme `[config error: <chemin>: <message>]`, code | ||
| 172 | 192 | | `context.summaryMaxTokens must be > 0` | zéro ou négatif | |
| 173 | 193 | | `context.maxMessages must be >= 0` | valeur négative | |
| 174 | 194 | | `sessions.dir must not be empty` | chaîne vide | |
| 195 | +| `agentsMd.path must not be empty` | chaîne vide avec `agentsMd.enabled` actif | | |
| 196 | +| `agentsMd.maxChars must be >= 0` | valeur négative | | |
| 175 | 197 | | `trace.dir must not be empty` | chaîne vide avec la trace active | |
| 176 | 198 | | `acp.name must not be empty` | chaîne vide ou blanche | |
| 177 | 199 | | `lsp.servers[N].name must not be empty` | un serveur sans `name` | |
| @@ -29,6 +29,7 @@ | |||
| 29 | | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. | | 29 | | `bashTimeout` | durée | `30s` | Durée maximale d'une commande `bash` avant qu'elle soit tuée ; l'outil répond alors `[error: <bashTimeout> timeout exceeded]`. Doit être > 0. Le watchdog continue de compter pendant qu'une commande tourne (ticket 57) : une commande plus longue que `watchdogTimeout` termine le tour, donc relevez les deux ensemble. | |
| 30 | | `context` | dictionnaire | voir ci-dessous | Compression automatique de l'historique. | | 30 | | `context` | dictionnaire | voir ci-dessous | Compression automatique de l'historique. | |
| 31 | | `sessions` | dictionnaire | voir ci-dessous | Sessions persistantes, par projet. | | 31 | | `sessions` | dictionnaire | voir ci-dessous | Sessions persistantes, par projet. | |
| 32 | +| `agentsMd` | dictionnaire | voir ci-dessous | Ajoute le `AGENTS.md` du projet au prompt système de chaque conversation. Actif par défaut. | | ||
| 32 | | `trace` | dictionnaire | voir ci-dessous | Écrit chaque requête au modèle et chaque réponse dans des fichiers. | | 33 | | `trace` | dictionnaire | voir ci-dessous | Écrit chaque requête au modèle et chaque réponse dans des fichiers. | |
| 33 | | `acp` | dictionnaire | voir ci-dessous | La façon dont `mm -acp` se présente à l'éditeur. | | 34 | | `acp` | dictionnaire | voir ci-dessous | La façon dont `mm -acp` se présente à l'éditeur. | |
| 34 | | `lsp` | dictionnaire | voir ci-dessous | Serveurs de langage derrière l'outil `lsp`. Aucun serveur listé = pas d'outil. | | 35 | | `lsp` | dictionnaire | voir ci-dessous | Serveurs de langage derrière l'outil `lsp`. Aucun serveur listé = pas d'outil. | |
| @@ -65,6 +66,25 @@ | |||
| 65 | 66 | ||
| 66 | Noms des fichiers : `<AAAAMMJJ-HHMMSS.mmm>-<acp.name>-<NNNN>.request.json`, puis `….response-<statut>.sse` (une réponse en streaming, les lignes SSE du serveur octet pour octet, écrites à leur arrivée), `….response-<statut>.json` (un corps JSON entier, indenté), ou `….response-error.txt` (pas de réponse : la méthode, l'URL et l'erreur). `NNNN` compte les appels au modèle du processus ; une nouvelle tentative du client HTTP est un appel à part. Un agent lancé par `/ask` ou `ask_agent` hérite de `AGENT_TRACE` et écrit à côté de celui qui l'interroge, sous son propre `acp.name`. | 67 | Noms des fichiers : `<AAAAMMJJ-HHMMSS.mmm>-<acp.name>-<NNNN>.request.json`, puis `….response-<statut>.sse` (une réponse en streaming, les lignes SSE du serveur octet pour octet, écrites à leur arrivée), `….response-<statut>.json` (un corps JSON entier, indenté), ou `….response-error.txt` (pas de réponse : la méthode, l'URL et l'erreur). `NNNN` compte les appels au modèle du processus ; une nouvelle tentative du client HTTP est un appel à part. Un agent lancé par `/ask` ou `ask_agent` hérite de `AGENT_TRACE` et écrit à côté de celui qui l'interroge, sous son propre `acp.name`. |
| 67 | 68 | ||
| 69 | +## Clés de `agentsMd` | ||
| 70 | + | ||
| 71 | +| Clé | Type | Défaut | Description | | ||
| 72 | +|-----|------|--------|-------------| | ||
| 73 | +| `agentsMd.enabled` | booléen | `true` | Lit le fichier au début de chaque conversation : démarrage, `/new`, `/resume`, une nouvelle session ACP (`session/new`), une session rechargée (`session/load`), `-p`. Jamais au milieu d'une conversation. Un agent démarré par `/ask`, `ask_agent` ou `mm --ask` ne le lit que si son propre fichier l'active ; il cherche dans le répertoire de travail qu'on lui donne (celui de l'agent qui l'interroge). | | ||
| 74 | +| `agentsMd.path` | chaîne | `AGENTS.md` | Le fichier. Un chemin relatif est résolu par rapport au répertoire de travail de la conversation — le répertoire où tourne `mm`, le `cwd` de la session en mode ACP — et non par rapport au fichier de configuration. Un chemin absolu est utilisé tel quel. Ne doit pas être vide quand `enabled` vaut `true`. | | ||
| 75 | +| `agentsMd.maxChars` | entier | `0` | Au-delà de ce nombre de caractères (caractères Unicode, après suppression des blancs aux extrémités), le fichier n'est pas lu du tout, et la bannière le dit. Jamais tronqué. `0` = pas de limite. Doit être ≥ 0. | | ||
| 76 | + | ||
| 77 | +Quand le fichier est lu, le message système est le prompt `system`, une ligne vide, puis : | ||
| 78 | + | ||
| 79 | +```text | ||
| 80 | +PROJECT INSTRUCTIONS (from <chemin absolu>) | ||
| 81 | +The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins. | ||
| 82 | + | ||
| 83 | +<le fichier, sans blancs aux extrémités> | ||
| 84 | +``` | ||
| 85 | + | ||
| 86 | +Non lu : un fichier absent, un fichier qui ne contient que des blancs, un fichier au-delà de `maxChars`, tout fichier quand `structuredOutput` est défini, un fichier illisible (avertissement de bannière `<chemin> cannot be read: …`). Une session reprise (`/resume`, `session/load`) continue sous le fichier tel qu'il est maintenant. | ||
| 87 | + | ||
| 68 | ## Clés de `acp` | 88 | ## Clés de `acp` |
| 69 | 89 | ||
| 70 | | Clé | Type | Défaut | Description | | 90 | | Clé | Type | Défaut | Description | |
| @@ -172,6 +192,8 @@ Signalées sur stderr sous la forme `[config error: <chemin>: <message>]`, code | |||
| 172 | | `context.summaryMaxTokens must be > 0` | zéro ou négatif | | 192 | | `context.summaryMaxTokens must be > 0` | zéro ou négatif | |
| 173 | | `context.maxMessages must be >= 0` | valeur négative | | 193 | | `context.maxMessages must be >= 0` | valeur négative | |
| 174 | | `sessions.dir must not be empty` | chaîne vide | | 194 | | `sessions.dir must not be empty` | chaîne vide | |
| 195 | +| `agentsMd.path must not be empty` | chaîne vide avec `agentsMd.enabled` actif | | ||
| 196 | +| `agentsMd.maxChars must be >= 0` | valeur négative | | ||
| 175 | | `trace.dir must not be empty` | chaîne vide avec la trace active | | 197 | | `trace.dir must not be empty` | chaîne vide avec la trace active | |
| 176 | | `acp.name must not be empty` | chaîne vide ou blanche | | 198 | | `acp.name must not be empty` | chaîne vide ou blanche | |
| 177 | | `lsp.servers[N].name must not be empty` | un serveur sans `name` | | 199 | | `lsp.servers[N].name must not be empty` | un serveur sans `name` | |
modified
internal/acp/acp.go +8 -4 | @@ -29,6 +29,7 @@ import ( | ||
| 29 | 29 | "strings" |
| 30 | 30 | "sync" |
| 31 | 31 | |
| 32 | + "mm/internal/agentsmd" | |
| 32 | 33 | "mm/internal/ask" |
| 33 | 34 | "mm/internal/command" |
| 34 | 35 | "mm/internal/compact" |
| @@ -235,8 +236,9 @@ func (f *front) NewSession(ctx context.Context, p sdk.NewSessionRequest) (sdk.Ne | ||
| 235 | 236 | // persisted, so both modes log the same kind of id. |
| 236 | 237 | sid := sdk.SessionId(store.NewID()) |
| 237 | 238 | s := &session{ |
| 238 | - cwd: p.Cwd, // absolute, per the spec; where bash runs for this session | |
| 239 | - messages: history.Fresh(f.system), | |
| 239 | + cwd: p.Cwd, // absolute, per the spec; where bash runs for this session | |
| 240 | + // The AGENTS.md of THIS session's project: each session has its own cwd. | |
| 241 | + messages: history.Fresh(agentsmd.System(f.system, p.Cwd)), | |
| 240 | 242 | } |
| 241 | 243 | f.persistNew(sid, s) |
| 242 | 244 | f.mu.Lock() |
| @@ -561,10 +563,12 @@ func compactHistory(ctx context.Context, s *session, cfg config.ContextConfig, s | ||
| 561 | 563 | |
| 562 | 564 | // resetSession replaces the session's history with a fresh one and returns how |
| 563 | 565 | // many messages were forgotten. Kept apart from startNewSession so the rule |
| 564 | -// — history reset, permissions kept — is testable without a connection. | |
| 566 | +// — history reset, permissions kept — is testable without a connection. The | |
| 567 | +// project's AGENTS.md is read again, from the session's cwd: an edited file | |
| 568 | +// is picked up by the new conversation. | |
| 565 | 569 | func resetSession(s *session, system string) int { |
| 566 | 570 | forgotten := history.Forgotten(s.messages) |
| 567 | - s.messages = history.Fresh(system) | |
| 571 | + s.messages = history.Fresh(agentsmd.System(system, s.cwd)) | |
| 568 | 572 | return forgotten |
| 569 | 573 | } |
| 570 | 574 | |
| @@ -29,6 +29,7 @@ import ( | |||
| 29 | "strings" | 29 | "strings" |
| 30 | "sync" | 30 | "sync" |
| 31 | 31 | ||
| 32 | + "mm/internal/agentsmd" | ||
| 32 | "mm/internal/ask" | 33 | "mm/internal/ask" |
| 33 | "mm/internal/command" | 34 | "mm/internal/command" |
| 34 | "mm/internal/compact" | 35 | "mm/internal/compact" |
| @@ -235,8 +236,9 @@ func (f *front) NewSession(ctx context.Context, p sdk.NewSessionRequest) (sdk.Ne | |||
| 235 | // persisted, so both modes log the same kind of id. | 236 | // persisted, so both modes log the same kind of id. |
| 236 | sid := sdk.SessionId(store.NewID()) | 237 | sid := sdk.SessionId(store.NewID()) |
| 237 | s := &session{ | 238 | s := &session{ |
| 238 | - cwd: p.Cwd, // absolute, per the spec; where bash runs for this session | 239 | + cwd: p.Cwd, // absolute, per the spec; where bash runs for this session |
| 239 | - messages: history.Fresh(f.system), | 240 | + // The AGENTS.md of THIS session's project: each session has its own cwd. |
| 241 | + messages: history.Fresh(agentsmd.System(f.system, p.Cwd)), | ||
| 240 | } | 242 | } |
| 241 | f.persistNew(sid, s) | 243 | f.persistNew(sid, s) |
| 242 | f.mu.Lock() | 244 | f.mu.Lock() |
| @@ -561,10 +563,12 @@ func compactHistory(ctx context.Context, s *session, cfg config.ContextConfig, s | |||
| 561 | 563 | ||
| 562 | // resetSession replaces the session's history with a fresh one and returns how | 564 | // resetSession replaces the session's history with a fresh one and returns how |
| 563 | // many messages were forgotten. Kept apart from startNewSession so the rule | 565 | // many messages were forgotten. Kept apart from startNewSession so the rule |
| 564 | -// — history reset, permissions kept — is testable without a connection. | 566 | +// — history reset, permissions kept — is testable without a connection. The |
| 567 | +// project's AGENTS.md is read again, from the session's cwd: an edited file | ||
| 568 | +// is picked up by the new conversation. | ||
| 565 | func resetSession(s *session, system string) int { | 569 | func resetSession(s *session, system string) int { |
| 566 | forgotten := history.Forgotten(s.messages) | 570 | forgotten := history.Forgotten(s.messages) |
| 567 | - s.messages = history.Fresh(system) | 571 | + s.messages = history.Fresh(agentsmd.System(system, s.cwd)) |
| 568 | return forgotten | 572 | return forgotten |
| 569 | } | 573 | } |
| 570 | 574 | ||
added
internal/acp/agentsmd_test.go +76 -0 | new file mode 100644 | ||
| @@ -0,0 +1,76 @@ | ||
| 1 | +package acp | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "encoding/json" | |
| 5 | + "os" | |
| 6 | + "path/filepath" | |
| 7 | + "strings" | |
| 8 | + "testing" | |
| 9 | + | |
| 10 | + "github.com/firebase/genkit/go/ai" | |
| 11 | + | |
| 12 | + "mm/internal/config" | |
| 13 | + history "mm/internal/session" | |
| 14 | + "mm/internal/store" | |
| 15 | +) | |
| 16 | + | |
| 17 | +// withAgentsMd turns the AGENTS.md reading on with its defaults for one test. | |
| 18 | +func withAgentsMd(t *testing.T) { | |
| 19 | + t.Helper() | |
| 20 | + saved := config.Cfg.AgentsMd | |
| 21 | + config.Cfg.AgentsMd = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | |
| 22 | + t.Cleanup(func() { config.Cfg.AgentsMd = saved }) | |
| 23 | +} | |
| 24 | + | |
| 25 | +// writeAgentsMd puts an AGENTS.md holding text in dir. | |
| 26 | +func writeAgentsMd(t *testing.T, dir, text string) { | |
| 27 | + t.Helper() | |
| 28 | + if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte(text), 0o644); err != nil { | |
| 29 | + t.Fatal(err) | |
| 30 | + } | |
| 31 | +} | |
| 32 | + | |
| 33 | +// Each ACP session reads the AGENTS.md of ITS cwd — the project the editor | |
| 34 | +// opened — not the directory the agent process runs in. | |
| 35 | +func TestNewSessionReadsTheAgentsMdOfItsCwd(t *testing.T) { | |
| 36 | + withSessions(t) | |
| 37 | + withAgentsMd(t) | |
| 38 | + cwd := t.TempDir() | |
| 39 | + writeAgentsMd(t, cwd, "Run `make test` before committing.") | |
| 40 | + w := newWire(t, "sys") | |
| 41 | + w.send(1, "initialize", `{"protocolVersion":1,"clientCapabilities":{}}`) | |
| 42 | + w.until(1) | |
| 43 | + w.send(2, "session/new", `{"cwd":"`+cwd+`","mcpServers":[]}`) | |
| 44 | + _, resp := w.until(2) | |
| 45 | + sid := resp["result"].(map[string]any)["sessionId"].(string) | |
| 46 | + | |
| 47 | + data, err := os.ReadFile(filepath.Join(cwd, ".mm", "sessions", sid+".json")) | |
| 48 | + if err != nil { | |
| 49 | + t.Fatal(err) | |
| 50 | + } | |
| 51 | + var rec store.Session | |
| 52 | + if err := json.Unmarshal(data, &rec); err != nil || len(rec.Messages) != 1 { | |
| 53 | + t.Fatalf("stored %+v (%v)", rec, err) | |
| 54 | + } | |
| 55 | + system := rec.Messages[0].Text() | |
| 56 | + if !strings.HasPrefix(system, "sys\n\nPROJECT INSTRUCTIONS (from "+filepath.Join(cwd, "AGENTS.md")+")") || !strings.Contains(system, "make test") { | |
| 57 | + t.Errorf("system message:\n%s", system) | |
| 58 | + } | |
| 59 | +} | |
| 60 | + | |
| 61 | +// /new reads the file again: an edit made during the conversation reaches | |
| 62 | +// the next one. | |
| 63 | +func TestResetSessionRereadsTheAgentsMd(t *testing.T) { | |
| 64 | + withAgentsMd(t) | |
| 65 | + cwd := t.TempDir() | |
| 66 | + writeAgentsMd(t, cwd, "first rules") | |
| 67 | + s := &session{cwd: cwd, messages: history.Fresh("you are bob")} | |
| 68 | + s.messages = append(s.messages, ai.NewUserTextMessage("ls")) | |
| 69 | + writeAgentsMd(t, cwd, "second rules") | |
| 70 | + | |
| 71 | + resetSession(s, "you are bob") | |
| 72 | + | |
| 73 | + if got := s.messages[0].Text(); !strings.Contains(got, "second rules") || !strings.HasPrefix(got, "you are bob\n\nPROJECT INSTRUCTIONS") { | |
| 74 | + t.Errorf("system after /new:\n%s", got) | |
| 75 | + } | |
| 76 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | +package acp | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "encoding/json" | ||
| 5 | + "os" | ||
| 6 | + "path/filepath" | ||
| 7 | + "strings" | ||
| 8 | + "testing" | ||
| 9 | + | ||
| 10 | + "github.com/firebase/genkit/go/ai" | ||
| 11 | + | ||
| 12 | + "mm/internal/config" | ||
| 13 | + history "mm/internal/session" | ||
| 14 | + "mm/internal/store" | ||
| 15 | +) | ||
| 16 | + | ||
| 17 | +// withAgentsMd turns the AGENTS.md reading on with its defaults for one test. | ||
| 18 | +func withAgentsMd(t *testing.T) { | ||
| 19 | + t.Helper() | ||
| 20 | + saved := config.Cfg.AgentsMd | ||
| 21 | + config.Cfg.AgentsMd = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | ||
| 22 | + t.Cleanup(func() { config.Cfg.AgentsMd = saved }) | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +// writeAgentsMd puts an AGENTS.md holding text in dir. | ||
| 26 | +func writeAgentsMd(t *testing.T, dir, text string) { | ||
| 27 | + t.Helper() | ||
| 28 | + if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte(text), 0o644); err != nil { | ||
| 29 | + t.Fatal(err) | ||
| 30 | + } | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +// Each ACP session reads the AGENTS.md of ITS cwd — the project the editor | ||
| 34 | +// opened — not the directory the agent process runs in. | ||
| 35 | +func TestNewSessionReadsTheAgentsMdOfItsCwd(t *testing.T) { | ||
| 36 | + withSessions(t) | ||
| 37 | + withAgentsMd(t) | ||
| 38 | + cwd := t.TempDir() | ||
| 39 | + writeAgentsMd(t, cwd, "Run `make test` before committing.") | ||
| 40 | + w := newWire(t, "sys") | ||
| 41 | + w.send(1, "initialize", `{"protocolVersion":1,"clientCapabilities":{}}`) | ||
| 42 | + w.until(1) | ||
| 43 | + w.send(2, "session/new", `{"cwd":"`+cwd+`","mcpServers":[]}`) | ||
| 44 | + _, resp := w.until(2) | ||
| 45 | + sid := resp["result"].(map[string]any)["sessionId"].(string) | ||
| 46 | + | ||
| 47 | + data, err := os.ReadFile(filepath.Join(cwd, ".mm", "sessions", sid+".json")) | ||
| 48 | + if err != nil { | ||
| 49 | + t.Fatal(err) | ||
| 50 | + } | ||
| 51 | + var rec store.Session | ||
| 52 | + if err := json.Unmarshal(data, &rec); err != nil || len(rec.Messages) != 1 { | ||
| 53 | + t.Fatalf("stored %+v (%v)", rec, err) | ||
| 54 | + } | ||
| 55 | + system := rec.Messages[0].Text() | ||
| 56 | + if !strings.HasPrefix(system, "sys\n\nPROJECT INSTRUCTIONS (from "+filepath.Join(cwd, "AGENTS.md")+")") || !strings.Contains(system, "make test") { | ||
| 57 | + t.Errorf("system message:\n%s", system) | ||
| 58 | + } | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +// /new reads the file again: an edit made during the conversation reaches | ||
| 62 | +// the next one. | ||
| 63 | +func TestResetSessionRereadsTheAgentsMd(t *testing.T) { | ||
| 64 | + withAgentsMd(t) | ||
| 65 | + cwd := t.TempDir() | ||
| 66 | + writeAgentsMd(t, cwd, "first rules") | ||
| 67 | + s := &session{cwd: cwd, messages: history.Fresh("you are bob")} | ||
| 68 | + s.messages = append(s.messages, ai.NewUserTextMessage("ls")) | ||
| 69 | + writeAgentsMd(t, cwd, "second rules") | ||
| 70 | + | ||
| 71 | + resetSession(s, "you are bob") | ||
| 72 | + | ||
| 73 | + if got := s.messages[0].Text(); !strings.Contains(got, "second rules") || !strings.HasPrefix(got, "you are bob\n\nPROJECT INSTRUCTIONS") { | ||
| 74 | + t.Errorf("system after /new:\n%s", got) | ||
| 75 | + } | ||
| 76 | +} | ||
modified
internal/acp/persist.go +2 -1 | @@ -7,6 +7,7 @@ import ( | ||
| 7 | 7 | "path/filepath" |
| 8 | 8 | "time" |
| 9 | 9 | |
| 10 | + "mm/internal/agentsmd" | |
| 10 | 11 | history "mm/internal/session" |
| 11 | 12 | "mm/internal/store" |
| 12 | 13 | |
| @@ -43,7 +44,7 @@ func (f *front) LoadSession(ctx context.Context, p sdk.LoadSessionRequest) (sdk. | ||
| 43 | 44 | // The stored history continues under TODAY's system prompt: the file may |
| 44 | 45 | // predate a change to agent.yaml, and the prompt is the agent's, not the |
| 45 | 46 | // conversation's. |
| 46 | - rec.Messages = history.WithSystem(rec.Messages, f.system) | |
| 47 | + rec.Messages = history.WithSystem(rec.Messages, agentsmd.System(f.system, p.Cwd)) | |
| 47 | 48 | s := &session{cwd: p.Cwd, messages: rec.Messages, record: rec, st: st} |
| 48 | 49 | f.mu.Lock() |
| 49 | 50 | f.sessions[p.SessionId] = s |
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | "path/filepath" | 7 | "path/filepath" |
| 8 | "time" | 8 | "time" |
| 9 | 9 | ||
| 10 | + "mm/internal/agentsmd" | ||
| 10 | history "mm/internal/session" | 11 | history "mm/internal/session" |
| 11 | "mm/internal/store" | 12 | "mm/internal/store" |
| 12 | 13 | ||
| @@ -43,7 +44,7 @@ func (f *front) LoadSession(ctx context.Context, p sdk.LoadSessionRequest) (sdk. | |||
| 43 | // The stored history continues under TODAY's system prompt: the file may | 44 | // The stored history continues under TODAY's system prompt: the file may |
| 44 | // predate a change to agent.yaml, and the prompt is the agent's, not the | 45 | // predate a change to agent.yaml, and the prompt is the agent's, not the |
| 45 | // conversation's. | 46 | // conversation's. |
| 46 | - rec.Messages = history.WithSystem(rec.Messages, f.system) | 47 | + rec.Messages = history.WithSystem(rec.Messages, agentsmd.System(f.system, p.Cwd)) |
| 47 | s := &session{cwd: p.Cwd, messages: rec.Messages, record: rec, st: st} | 48 | s := &session{cwd: p.Cwd, messages: rec.Messages, record: rec, st: st} |
| 48 | f.mu.Lock() | 49 | f.mu.Lock() |
| 49 | f.sessions[p.SessionId] = s | 50 | f.sessions[p.SessionId] = s |
modified
internal/agent/agent.go +7 -4 | @@ -10,6 +10,7 @@ import ( | ||
| 10 | 10 | "os/signal" |
| 11 | 11 | "strings" |
| 12 | 12 | |
| 13 | + "mm/internal/agentsmd" | |
| 13 | 14 | "mm/internal/ask" |
| 14 | 15 | "mm/internal/clipboard" |
| 15 | 16 | "mm/internal/command" |
| @@ -43,7 +44,9 @@ type genResult struct { | ||
| 43 | 44 | // |
| 44 | 45 | // pool runs the /ask commands (another mm agent); nil when none is configured. |
| 45 | 46 | func Run(ctx context.Context, e *engine.Engine, system string, tools []ai.ToolRef, d *detector.LoopDetector, pool ask.Runner) { |
| 46 | - messages := session.Fresh(system) | |
| 47 | + // The project's AGENTS.md joins the prompt at the start of every | |
| 48 | + // conversation — here, /new and /resume — never in the middle of one. | |
| 49 | + messages := session.Fresh(agentsmd.System(system, "")) | |
| 47 | 50 | ctx, cancelAll := context.WithCancel(ctx) |
| 48 | 51 | defer cancelAll() |
| 49 | 52 | |
| @@ -484,7 +487,7 @@ func startNewSession(e *engine.Engine, d *detector.LoopDetector, system string, | ||
| 484 | 487 | // Clear any active plans when starting a new session |
| 485 | 488 | tools.ClearAllPlans() |
| 486 | 489 | dimln(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten)) |
| 487 | - return session.Fresh(system) | |
| 490 | + return session.Fresh(agentsmd.System(system, "")) | |
| 488 | 491 | } |
| 489 | 492 | |
| 490 | 493 | // dimln prints one line, dimmed on a terminal and plain in a pipe — the same |
| @@ -531,7 +534,7 @@ func (l *loop) historyCommand(input string, messages []*ai.Message) ([]*ai.Messa | ||
| 531 | 534 | case command.Copy: |
| 532 | 535 | fmt.Println(copyAnswer(messages, arg, clipboard.New(terminalOSC52()))) |
| 533 | 536 | case command.Resume: |
| 534 | - resumed, line, err := l.keep.Resume(arg, l.system) | |
| 537 | + resumed, line, err := l.keep.Resume(arg, agentsmd.System(l.system, "")) | |
| 535 | 538 | if err != nil { |
| 536 | 539 | fmt.Println("⚠️ " + err.Error()) |
| 537 | 540 | record(l.keep, messages, store.NewFailure(messages, store.FailResume, err.Error(), err)) |
| @@ -550,7 +553,7 @@ func (l *loop) historyCommand(input string, messages []*ai.Message) ([]*ai.Messa | ||
| 550 | 553 | // RunPrompt executes a single prompt and displays the result, then exits. |
| 551 | 554 | // This is the one-shot mode for running a single prompt without interaction. |
| 552 | 555 | func RunPrompt(ctx context.Context, e *engine.Engine, system string, tools []ai.ToolRef, d *detector.LoopDetector, prompt string) { |
| 553 | - messages := session.Fresh(system) | |
| 556 | + messages := session.Fresh(agentsmd.System(system, "")) | |
| 554 | 557 | |
| 555 | 558 | // Add the user's prompt to the history |
| 556 | 559 | messages = append(messages, ai.NewUserTextMessage(prompt)) |
| @@ -10,6 +10,7 @@ import ( | |||
| 10 | "os/signal" | 10 | "os/signal" |
| 11 | "strings" | 11 | "strings" |
| 12 | 12 | ||
| 13 | + "mm/internal/agentsmd" | ||
| 13 | "mm/internal/ask" | 14 | "mm/internal/ask" |
| 14 | "mm/internal/clipboard" | 15 | "mm/internal/clipboard" |
| 15 | "mm/internal/command" | 16 | "mm/internal/command" |
| @@ -43,7 +44,9 @@ type genResult struct { | |||
| 43 | // | 44 | // |
| 44 | // pool runs the /ask commands (another mm agent); nil when none is configured. | 45 | // pool runs the /ask commands (another mm agent); nil when none is configured. |
| 45 | func Run(ctx context.Context, e *engine.Engine, system string, tools []ai.ToolRef, d *detector.LoopDetector, pool ask.Runner) { | 46 | func Run(ctx context.Context, e *engine.Engine, system string, tools []ai.ToolRef, d *detector.LoopDetector, pool ask.Runner) { |
| 46 | - messages := session.Fresh(system) | 47 | + // The project's AGENTS.md joins the prompt at the start of every |
| 48 | + // conversation — here, /new and /resume — never in the middle of one. | ||
| 49 | + messages := session.Fresh(agentsmd.System(system, "")) | ||
| 47 | ctx, cancelAll := context.WithCancel(ctx) | 50 | ctx, cancelAll := context.WithCancel(ctx) |
| 48 | defer cancelAll() | 51 | defer cancelAll() |
| 49 | 52 | ||
| @@ -484,7 +487,7 @@ func startNewSession(e *engine.Engine, d *detector.LoopDetector, system string, | |||
| 484 | // Clear any active plans when starting a new session | 487 | // Clear any active plans when starting a new session |
| 485 | tools.ClearAllPlans() | 488 | tools.ClearAllPlans() |
| 486 | dimln(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten)) | 489 | dimln(fmt.Sprintf("🆕 new session: %d message(s) forgotten", forgotten)) |
| 487 | - return session.Fresh(system) | 490 | + return session.Fresh(agentsmd.System(system, "")) |
| 488 | } | 491 | } |
| 489 | 492 | ||
| 490 | // dimln prints one line, dimmed on a terminal and plain in a pipe — the same | 493 | // dimln prints one line, dimmed on a terminal and plain in a pipe — the same |
| @@ -531,7 +534,7 @@ func (l *loop) historyCommand(input string, messages []*ai.Message) ([]*ai.Messa | |||
| 531 | case command.Copy: | 534 | case command.Copy: |
| 532 | fmt.Println(copyAnswer(messages, arg, clipboard.New(terminalOSC52()))) | 535 | fmt.Println(copyAnswer(messages, arg, clipboard.New(terminalOSC52()))) |
| 533 | case command.Resume: | 536 | case command.Resume: |
| 534 | - resumed, line, err := l.keep.Resume(arg, l.system) | 537 | + resumed, line, err := l.keep.Resume(arg, agentsmd.System(l.system, "")) |
| 535 | if err != nil { | 538 | if err != nil { |
| 536 | fmt.Println("⚠️ " + err.Error()) | 539 | fmt.Println("⚠️ " + err.Error()) |
| 537 | record(l.keep, messages, store.NewFailure(messages, store.FailResume, err.Error(), err)) | 540 | record(l.keep, messages, store.NewFailure(messages, store.FailResume, err.Error(), err)) |
| @@ -550,7 +553,7 @@ func (l *loop) historyCommand(input string, messages []*ai.Message) ([]*ai.Messa | |||
| 550 | // RunPrompt executes a single prompt and displays the result, then exits. | 553 | // RunPrompt executes a single prompt and displays the result, then exits. |
| 551 | // This is the one-shot mode for running a single prompt without interaction. | 554 | // This is the one-shot mode for running a single prompt without interaction. |
| 552 | func RunPrompt(ctx context.Context, e *engine.Engine, system string, tools []ai.ToolRef, d *detector.LoopDetector, prompt string) { | 555 | func RunPrompt(ctx context.Context, e *engine.Engine, system string, tools []ai.ToolRef, d *detector.LoopDetector, prompt string) { |
| 553 | - messages := session.Fresh(system) | 556 | + messages := session.Fresh(agentsmd.System(system, "")) |
| 554 | 557 | ||
| 555 | // Add the user's prompt to the history | 558 | // Add the user's prompt to the history |
| 556 | messages = append(messages, ai.NewUserTextMessage(prompt)) | 559 | messages = append(messages, ai.NewUserTextMessage(prompt)) |
added
internal/agent/agentsmd_test.go +32 -0 | new file mode 100644 | ||
| @@ -0,0 +1,32 @@ | ||
| 1 | +package agent | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "os" | |
| 5 | + "strings" | |
| 6 | + "testing" | |
| 7 | + | |
| 8 | + "github.com/firebase/genkit/go/ai" | |
| 9 | + | |
| 10 | + "mm/internal/config" | |
| 11 | + "mm/internal/detector" | |
| 12 | + "mm/internal/engine" | |
| 13 | +) | |
| 14 | + | |
| 15 | +// The line mode reads the AGENTS.md of the directory mm runs in, again on | |
| 16 | +// every /new: an edited file reaches the next conversation. | |
| 17 | +func TestNewSessionRereadsTheAgentsMdOfTheWorkingDirectory(t *testing.T) { | |
| 18 | + saved := config.Cfg.AgentsMd | |
| 19 | + config.Cfg.AgentsMd = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | |
| 20 | + t.Cleanup(func() { config.Cfg.AgentsMd = saved }) | |
| 21 | + t.Chdir(t.TempDir()) | |
| 22 | + if err := os.WriteFile("AGENTS.md", []byte("line mode rules"), 0o644); err != nil { | |
| 23 | + t.Fatal(err) | |
| 24 | + } | |
| 25 | + | |
| 26 | + fresh := startNewSession(&engine.Engine{}, detector.NewLoopDetector(10, 3), "you are bob", []*ai.Message{ai.NewUserTextMessage("hi")}) | |
| 27 | + | |
| 28 | + got := fresh[0].Text() | |
| 29 | + if !strings.HasPrefix(got, "you are bob\n\nPROJECT INSTRUCTIONS (from ") || !strings.Contains(got, "line mode rules") { | |
| 30 | + t.Errorf("system after /new:\n%s", got) | |
| 31 | + } | |
| 32 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | +package agent | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "os" | ||
| 5 | + "strings" | ||
| 6 | + "testing" | ||
| 7 | + | ||
| 8 | + "github.com/firebase/genkit/go/ai" | ||
| 9 | + | ||
| 10 | + "mm/internal/config" | ||
| 11 | + "mm/internal/detector" | ||
| 12 | + "mm/internal/engine" | ||
| 13 | +) | ||
| 14 | + | ||
| 15 | +// The line mode reads the AGENTS.md of the directory mm runs in, again on | ||
| 16 | +// every /new: an edited file reaches the next conversation. | ||
| 17 | +func TestNewSessionRereadsTheAgentsMdOfTheWorkingDirectory(t *testing.T) { | ||
| 18 | + saved := config.Cfg.AgentsMd | ||
| 19 | + config.Cfg.AgentsMd = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | ||
| 20 | + t.Cleanup(func() { config.Cfg.AgentsMd = saved }) | ||
| 21 | + t.Chdir(t.TempDir()) | ||
| 22 | + if err := os.WriteFile("AGENTS.md", []byte("line mode rules"), 0o644); err != nil { | ||
| 23 | + t.Fatal(err) | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + fresh := startNewSession(&engine.Engine{}, detector.NewLoopDetector(10, 3), "you are bob", []*ai.Message{ai.NewUserTextMessage("hi")}) | ||
| 27 | + | ||
| 28 | + got := fresh[0].Text() | ||
| 29 | + if !strings.HasPrefix(got, "you are bob\n\nPROJECT INSTRUCTIONS (from ") || !strings.Contains(got, "line mode rules") { | ||
| 30 | + t.Errorf("system after /new:\n%s", got) | ||
| 31 | + } | ||
| 32 | +} | ||
added
internal/agentsmd/agentsmd.go +134 -0 | new file mode 100644 | ||
| @@ -0,0 +1,134 @@ | ||
| 1 | +// Package agentsmd reads a project's AGENTS.md (https://agents.md) and adds | |
| 2 | +// it to the agent's system prompt (ticket 81). | |
| 3 | +// | |
| 4 | +// The config's `system` says what the agent is; AGENTS.md says what the | |
| 5 | +// project is — its build and test commands, its conventions, what not to | |
| 6 | +// touch. The two are joined, never swapped: the file's text follows the | |
| 7 | +// prompt under a heading that names where it came from. | |
| 8 | +// | |
| 9 | +// The file is read from the conversation's working directory — the | |
| 10 | +// directory mm runs in, or the session's cwd over ACP — at the start of every | |
| 11 | +// conversation: start-up, /new, /resume, a new or reloaded ACP session. Never | |
| 12 | +// in the middle of one: a system message that changes mid-way makes the | |
| 13 | +// server re-read the whole history (its prompt cache is keyed on the prefix). | |
| 14 | +// | |
| 15 | +// Settings: config.Cfg.AgentsMd (enabled, path, maxChars). An agent with a | |
| 16 | +// structuredOutput never reads it: prose instructions would fight the JSON | |
| 17 | +// constraint (ticket 80). | |
| 18 | +package agentsmd | |
| 19 | + | |
| 20 | +import ( | |
| 21 | + "errors" | |
| 22 | + "io/fs" | |
| 23 | + "os" | |
| 24 | + "path/filepath" | |
| 25 | + "strings" | |
| 26 | + "unicode/utf8" | |
| 27 | + | |
| 28 | + "mm/internal/config" | |
| 29 | +) | |
| 30 | + | |
| 31 | +// Status says what Load found. | |
| 32 | +type Status int | |
| 33 | + | |
| 34 | +const ( | |
| 35 | + // Disabled: `agentsMd.enabled: false`, nothing was looked for. | |
| 36 | + Disabled Status = iota | |
| 37 | + // Missing: no file at the path, or a file with nothing but blanks. | |
| 38 | + Missing | |
| 39 | + // Loaded: the file's text goes into the system prompt. | |
| 40 | + Loaded | |
| 41 | + // Skipped: the file is longer than agentsMd.maxChars. It is left out | |
| 42 | + // whole — never truncated, which could drop the one rule that matters. | |
| 43 | + Skipped | |
| 44 | + // Ignored: the file exists, but the agent has a structuredOutput. | |
| 45 | + Ignored | |
| 46 | + // Unreadable: the file exists but could not be read (Err says why). | |
| 47 | + Unreadable | |
| 48 | +) | |
| 49 | + | |
| 50 | +// File is the outcome of one Load. | |
| 51 | +type File struct { | |
| 52 | + Status Status | |
| 53 | + // Path is the absolute path looked at ("" when Disabled). | |
| 54 | + Path string | |
| 55 | + // Text is the file's content, trimmed; set when Loaded, Skipped or | |
| 56 | + // Ignored, so the banner can measure it. | |
| 57 | + Text string | |
| 58 | + // Err is the read error when Unreadable. | |
| 59 | + Err error | |
| 60 | +} | |
| 61 | + | |
| 62 | +// Load reads the AGENTS.md of workDir under the current settings. workDir | |
| 63 | +// "" means the directory mm runs in. | |
| 64 | +// | |
| 65 | +// f := agentsmd.Load("/home/me/project") | |
| 66 | +// if f.Status == agentsmd.Loaded { fmt.Println(f.Path, f.Tokens()) } | |
| 67 | +func Load(workDir string) File { | |
| 68 | + settings := config.Cfg.AgentsMd | |
| 69 | + if !settings.Enabled { | |
| 70 | + return File{Status: Disabled} | |
| 71 | + } | |
| 72 | + path := resolve(workDir, settings.Path) | |
| 73 | + data, err := os.ReadFile(path) | |
| 74 | + switch { | |
| 75 | + case errors.Is(err, fs.ErrNotExist): | |
| 76 | + return File{Status: Missing, Path: path} | |
| 77 | + case err != nil: | |
| 78 | + return File{Status: Unreadable, Path: path, Err: err} | |
| 79 | + } | |
| 80 | + text := strings.TrimSpace(string(data)) | |
| 81 | + switch { | |
| 82 | + case text == "": | |
| 83 | + return File{Status: Missing, Path: path} | |
| 84 | + case config.Cfg.OutputSchema != nil: | |
| 85 | + return File{Status: Ignored, Path: path, Text: text} | |
| 86 | + case settings.MaxChars > 0 && utf8.RuneCountInString(text) > settings.MaxChars: | |
| 87 | + return File{Status: Skipped, Path: path, Text: text} | |
| 88 | + } | |
| 89 | + return File{Status: Loaded, Path: path, Text: text} | |
| 90 | +} | |
| 91 | + | |
| 92 | +// System returns base with the AGENTS.md of workDir appended when there is | |
| 93 | +// one to add, base unchanged otherwise. It is what every front end hands | |
| 94 | +// to session.Fresh and session.WithSystem. | |
| 95 | +// | |
| 96 | +// messages := session.Fresh(agentsmd.System(config.Cfg.System, cwd)) | |
| 97 | +func System(base, workDir string) string { | |
| 98 | + f := Load(workDir) | |
| 99 | + if f.Status != Loaded { | |
| 100 | + return base | |
| 101 | + } | |
| 102 | + return strings.TrimRight(base, "\n") + "\n\n" + f.Section() | |
| 103 | +} | |
| 104 | + | |
| 105 | +// Section is the text appended to the system prompt: a heading naming the | |
| 106 | +// file, one line saying how to weigh it, then the file itself. | |
| 107 | +func (f File) Section() string { | |
| 108 | + return "PROJECT INSTRUCTIONS (from " + f.Path + ")\n" + | |
| 109 | + "The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins.\n\n" + | |
| 110 | + f.Text + "\n" | |
| 111 | +} | |
| 112 | + | |
| 113 | +// Chars is the length of the file's text, in characters. | |
| 114 | +func (f File) Chars() int { | |
| 115 | + return utf8.RuneCountInString(f.Text) | |
| 116 | +} | |
| 117 | + | |
| 118 | +// Tokens estimates the file's size in tokens: characters divided by 3.5, the | |
| 119 | +// same estimate as the context compression's (compact.Estimate). | |
| 120 | +func (f File) Tokens() int { | |
| 121 | + return (f.Chars()*2 + 6) / 7 | |
| 122 | +} | |
| 123 | + | |
| 124 | +// resolve makes the configured path absolute against workDir; an absolute | |
| 125 | +// path is kept as-is. | |
| 126 | +func resolve(workDir, path string) string { | |
| 127 | + if !filepath.IsAbs(path) { | |
| 128 | + path = filepath.Join(workDir, path) | |
| 129 | + } | |
| 130 | + if abs, err := filepath.Abs(path); err == nil { | |
| 131 | + return abs | |
| 132 | + } | |
| 133 | + return path | |
| 134 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,134 @@ | |||
| 1 | +// Package agentsmd reads a project's AGENTS.md (https://agents.md) and adds | ||
| 2 | +// it to the agent's system prompt (ticket 81). | ||
| 3 | +// | ||
| 4 | +// The config's `system` says what the agent is; AGENTS.md says what the | ||
| 5 | +// project is — its build and test commands, its conventions, what not to | ||
| 6 | +// touch. The two are joined, never swapped: the file's text follows the | ||
| 7 | +// prompt under a heading that names where it came from. | ||
| 8 | +// | ||
| 9 | +// The file is read from the conversation's working directory — the | ||
| 10 | +// directory mm runs in, or the session's cwd over ACP — at the start of every | ||
| 11 | +// conversation: start-up, /new, /resume, a new or reloaded ACP session. Never | ||
| 12 | +// in the middle of one: a system message that changes mid-way makes the | ||
| 13 | +// server re-read the whole history (its prompt cache is keyed on the prefix). | ||
| 14 | +// | ||
| 15 | +// Settings: config.Cfg.AgentsMd (enabled, path, maxChars). An agent with a | ||
| 16 | +// structuredOutput never reads it: prose instructions would fight the JSON | ||
| 17 | +// constraint (ticket 80). | ||
| 18 | +package agentsmd | ||
| 19 | + | ||
| 20 | +import ( | ||
| 21 | + "errors" | ||
| 22 | + "io/fs" | ||
| 23 | + "os" | ||
| 24 | + "path/filepath" | ||
| 25 | + "strings" | ||
| 26 | + "unicode/utf8" | ||
| 27 | + | ||
| 28 | + "mm/internal/config" | ||
| 29 | +) | ||
| 30 | + | ||
| 31 | +// Status says what Load found. | ||
| 32 | +type Status int | ||
| 33 | + | ||
| 34 | +const ( | ||
| 35 | + // Disabled: `agentsMd.enabled: false`, nothing was looked for. | ||
| 36 | + Disabled Status = iota | ||
| 37 | + // Missing: no file at the path, or a file with nothing but blanks. | ||
| 38 | + Missing | ||
| 39 | + // Loaded: the file's text goes into the system prompt. | ||
| 40 | + Loaded | ||
| 41 | + // Skipped: the file is longer than agentsMd.maxChars. It is left out | ||
| 42 | + // whole — never truncated, which could drop the one rule that matters. | ||
| 43 | + Skipped | ||
| 44 | + // Ignored: the file exists, but the agent has a structuredOutput. | ||
| 45 | + Ignored | ||
| 46 | + // Unreadable: the file exists but could not be read (Err says why). | ||
| 47 | + Unreadable | ||
| 48 | +) | ||
| 49 | + | ||
| 50 | +// File is the outcome of one Load. | ||
| 51 | +type File struct { | ||
| 52 | + Status Status | ||
| 53 | + // Path is the absolute path looked at ("" when Disabled). | ||
| 54 | + Path string | ||
| 55 | + // Text is the file's content, trimmed; set when Loaded, Skipped or | ||
| 56 | + // Ignored, so the banner can measure it. | ||
| 57 | + Text string | ||
| 58 | + // Err is the read error when Unreadable. | ||
| 59 | + Err error | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +// Load reads the AGENTS.md of workDir under the current settings. workDir | ||
| 63 | +// "" means the directory mm runs in. | ||
| 64 | +// | ||
| 65 | +// f := agentsmd.Load("/home/me/project") | ||
| 66 | +// if f.Status == agentsmd.Loaded { fmt.Println(f.Path, f.Tokens()) } | ||
| 67 | +func Load(workDir string) File { | ||
| 68 | + settings := config.Cfg.AgentsMd | ||
| 69 | + if !settings.Enabled { | ||
| 70 | + return File{Status: Disabled} | ||
| 71 | + } | ||
| 72 | + path := resolve(workDir, settings.Path) | ||
| 73 | + data, err := os.ReadFile(path) | ||
| 74 | + switch { | ||
| 75 | + case errors.Is(err, fs.ErrNotExist): | ||
| 76 | + return File{Status: Missing, Path: path} | ||
| 77 | + case err != nil: | ||
| 78 | + return File{Status: Unreadable, Path: path, Err: err} | ||
| 79 | + } | ||
| 80 | + text := strings.TrimSpace(string(data)) | ||
| 81 | + switch { | ||
| 82 | + case text == "": | ||
| 83 | + return File{Status: Missing, Path: path} | ||
| 84 | + case config.Cfg.OutputSchema != nil: | ||
| 85 | + return File{Status: Ignored, Path: path, Text: text} | ||
| 86 | + case settings.MaxChars > 0 && utf8.RuneCountInString(text) > settings.MaxChars: | ||
| 87 | + return File{Status: Skipped, Path: path, Text: text} | ||
| 88 | + } | ||
| 89 | + return File{Status: Loaded, Path: path, Text: text} | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +// System returns base with the AGENTS.md of workDir appended when there is | ||
| 93 | +// one to add, base unchanged otherwise. It is what every front end hands | ||
| 94 | +// to session.Fresh and session.WithSystem. | ||
| 95 | +// | ||
| 96 | +// messages := session.Fresh(agentsmd.System(config.Cfg.System, cwd)) | ||
| 97 | +func System(base, workDir string) string { | ||
| 98 | + f := Load(workDir) | ||
| 99 | + if f.Status != Loaded { | ||
| 100 | + return base | ||
| 101 | + } | ||
| 102 | + return strings.TrimRight(base, "\n") + "\n\n" + f.Section() | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +// Section is the text appended to the system prompt: a heading naming the | ||
| 106 | +// file, one line saying how to weigh it, then the file itself. | ||
| 107 | +func (f File) Section() string { | ||
| 108 | + return "PROJECT INSTRUCTIONS (from " + f.Path + ")\n" + | ||
| 109 | + "The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins.\n\n" + | ||
| 110 | + f.Text + "\n" | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +// Chars is the length of the file's text, in characters. | ||
| 114 | +func (f File) Chars() int { | ||
| 115 | + return utf8.RuneCountInString(f.Text) | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +// Tokens estimates the file's size in tokens: characters divided by 3.5, the | ||
| 119 | +// same estimate as the context compression's (compact.Estimate). | ||
| 120 | +func (f File) Tokens() int { | ||
| 121 | + return (f.Chars()*2 + 6) / 7 | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +// resolve makes the configured path absolute against workDir; an absolute | ||
| 125 | +// path is kept as-is. | ||
| 126 | +func resolve(workDir, path string) string { | ||
| 127 | + if !filepath.IsAbs(path) { | ||
| 128 | + path = filepath.Join(workDir, path) | ||
| 129 | + } | ||
| 130 | + if abs, err := filepath.Abs(path); err == nil { | ||
| 131 | + return abs | ||
| 132 | + } | ||
| 133 | + return path | ||
| 134 | +} | ||
added
internal/agentsmd/agentsmd_test.go +165 -0 | new file mode 100644 | ||
| @@ -0,0 +1,165 @@ | ||
| 1 | +package agentsmd | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "os" | |
| 5 | + "path/filepath" | |
| 6 | + "strings" | |
| 7 | + "testing" | |
| 8 | + | |
| 9 | + "mm/internal/config" | |
| 10 | +) | |
| 11 | + | |
| 12 | +// withSettings sets config.Cfg.AgentsMd (and clears OutputSchema) for one test. | |
| 13 | +func withSettings(t *testing.T, s config.AgentsMdConfig) { | |
| 14 | + t.Helper() | |
| 15 | + old, oldSchema := config.Cfg.AgentsMd, config.Cfg.OutputSchema | |
| 16 | + config.Cfg.AgentsMd, config.Cfg.OutputSchema = s, nil | |
| 17 | + t.Cleanup(func() { config.Cfg.AgentsMd, config.Cfg.OutputSchema = old, oldSchema }) | |
| 18 | +} | |
| 19 | + | |
| 20 | +// project returns a directory holding an AGENTS.md with text. | |
| 21 | +func project(t *testing.T, text string) string { | |
| 22 | + t.Helper() | |
| 23 | + dir := t.TempDir() | |
| 24 | + if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte(text), 0o644); err != nil { | |
| 25 | + t.Fatal(err) | |
| 26 | + } | |
| 27 | + return dir | |
| 28 | +} | |
| 29 | + | |
| 30 | +var defaults = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | |
| 31 | + | |
| 32 | +func TestTheFileJoinsTheSystemPromptUnderAHeading(t *testing.T) { | |
| 33 | + withSettings(t, defaults) | |
| 34 | + dir := project(t, "# Build\nRun `make test` before committing.\n") | |
| 35 | + | |
| 36 | + got := System("You are Bob.\n", dir) | |
| 37 | + | |
| 38 | + want := "You are Bob.\n\nPROJECT INSTRUCTIONS (from " + filepath.Join(dir, "AGENTS.md") + ")\n" | |
| 39 | + if !strings.HasPrefix(got, want) { | |
| 40 | + t.Errorf("system prompt:\n%s\nwant it to start with:\n%s", got, want) | |
| 41 | + } | |
| 42 | + if !strings.HasSuffix(got, "# Build\nRun `make test` before committing.\n") { | |
| 43 | + t.Errorf("the file's text is not at the end:\n%s", got) | |
| 44 | + } | |
| 45 | +} | |
| 46 | + | |
| 47 | +func TestNoFileLeavesThePromptUnchanged(t *testing.T) { | |
| 48 | + withSettings(t, defaults) | |
| 49 | + if got := System("You are Bob.", t.TempDir()); got != "You are Bob." { | |
| 50 | + t.Errorf("System = %q, want the base prompt", got) | |
| 51 | + } | |
| 52 | + if got := System("You are Bob.", project(t, " \n\n ")); got != "You are Bob." { | |
| 53 | + t.Errorf("a blank file: System = %q, want the base prompt", got) | |
| 54 | + } | |
| 55 | +} | |
| 56 | + | |
| 57 | +func TestDisabledReadsNothing(t *testing.T) { | |
| 58 | + withSettings(t, config.AgentsMdConfig{Enabled: false, Path: "AGENTS.md"}) | |
| 59 | + dir := project(t, "rules") | |
| 60 | + if f := Load(dir); f.Status != Disabled || f.Path != "" { | |
| 61 | + t.Errorf("Load = %+v, want Disabled", f) | |
| 62 | + } | |
| 63 | + if got := System("base", dir); got != "base" { | |
| 64 | + t.Errorf("System = %q, want the base prompt", got) | |
| 65 | + } | |
| 66 | +} | |
| 67 | + | |
| 68 | +// The path is relative to the working directory, not to the process's. | |
| 69 | +func TestThePathIsRelativeToTheWorkingDirectory(t *testing.T) { | |
| 70 | + withSettings(t, config.AgentsMdConfig{Enabled: true, Path: "docs/AGENTS.md"}) | |
| 71 | + dir := t.TempDir() | |
| 72 | + if err := os.MkdirAll(filepath.Join(dir, "docs"), 0o755); err != nil { | |
| 73 | + t.Fatal(err) | |
| 74 | + } | |
| 75 | + if err := os.WriteFile(filepath.Join(dir, "docs", "AGENTS.md"), []byte("docs rules"), 0o644); err != nil { | |
| 76 | + t.Fatal(err) | |
| 77 | + } | |
| 78 | + f := Load(dir) | |
| 79 | + if f.Status != Loaded || f.Path != filepath.Join(dir, "docs", "AGENTS.md") || f.Text != "docs rules" { | |
| 80 | + t.Errorf("Load = %+v", f) | |
| 81 | + } | |
| 82 | +} | |
| 83 | + | |
| 84 | +func TestAnAbsolutePathIsUsedAsIs(t *testing.T) { | |
| 85 | + dir := project(t, "absolute rules") | |
| 86 | + withSettings(t, config.AgentsMdConfig{Enabled: true, Path: filepath.Join(dir, "AGENTS.md")}) | |
| 87 | + if f := Load(t.TempDir()); f.Status != Loaded || f.Text != "absolute rules" { | |
| 88 | + t.Errorf("Load = %+v", f) | |
| 89 | + } | |
| 90 | +} | |
| 91 | + | |
| 92 | +// Beyond maxChars the file is left out whole, never truncated. | |
| 93 | +func TestAFileOverTheLimitIsSkippedWhole(t *testing.T) { | |
| 94 | + withSettings(t, config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md", MaxChars: 10}) | |
| 95 | + dir := project(t, "éééééééééééé") // 12 characters, 24 bytes | |
| 96 | + | |
| 97 | + f := Load(dir) | |
| 98 | + if f.Status != Skipped || f.Chars() != 12 { | |
| 99 | + t.Errorf("Load = %+v (chars %d), want Skipped with 12 characters", f, f.Chars()) | |
| 100 | + } | |
| 101 | + if got := System("base", dir); got != "base" { | |
| 102 | + t.Errorf("System = %q, want the base prompt untouched", got) | |
| 103 | + } | |
| 104 | + | |
| 105 | + withSettings(t, config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md", MaxChars: 12}) | |
| 106 | + if f := Load(dir); f.Status != Loaded { | |
| 107 | + t.Errorf("at the limit: Load = %+v, want Loaded", f) | |
| 108 | + } | |
| 109 | +} | |
| 110 | + | |
| 111 | +// A structured agent answers in JSON: it never reads prose instructions, | |
| 112 | +// but Load still reports the file so the banner can say it was ignored. | |
| 113 | +func TestAStructuredAgentIgnoresTheFile(t *testing.T) { | |
| 114 | + withSettings(t, defaults) | |
| 115 | + config.Cfg.OutputSchema = map[string]any{"type": "object"} | |
| 116 | + dir := project(t, "Answer in prose.") | |
| 117 | + | |
| 118 | + if f := Load(dir); f.Status != Ignored { | |
| 119 | + t.Errorf("Load = %+v, want Ignored", f) | |
| 120 | + } | |
| 121 | + if got := System("base", dir); got != "base" { | |
| 122 | + t.Errorf("System = %q, want the base prompt", got) | |
| 123 | + } | |
| 124 | + if f := Load(t.TempDir()); f.Status != Missing { | |
| 125 | + t.Errorf("no file: Load = %+v, want Missing (nothing to mention)", f) | |
| 126 | + } | |
| 127 | +} | |
| 128 | + | |
| 129 | +func TestAnUnreadableFileIsReported(t *testing.T) { | |
| 130 | + withSettings(t, defaults) | |
| 131 | + dir := t.TempDir() | |
| 132 | + // A directory where the file should be: os.ReadFile fails, not ErrNotExist. | |
| 133 | + if err := os.Mkdir(filepath.Join(dir, "AGENTS.md"), 0o755); err != nil { | |
| 134 | + t.Fatal(err) | |
| 135 | + } | |
| 136 | + if f := Load(dir); f.Status != Unreadable || f.Err == nil { | |
| 137 | + t.Errorf("Load = %+v, want Unreadable with its error", f) | |
| 138 | + } | |
| 139 | + if got := System("base", dir); got != "base" { | |
| 140 | + t.Errorf("System = %q, want the base prompt", got) | |
| 141 | + } | |
| 142 | +} | |
| 143 | + | |
| 144 | +// The file is read on every call: an edited file is picked up by the next | |
| 145 | +// conversation. | |
| 146 | +func TestAnEditedFileIsPickedUpByTheNextCall(t *testing.T) { | |
| 147 | + withSettings(t, defaults) | |
| 148 | + dir := project(t, "first version") | |
| 149 | + if !strings.Contains(System("base", dir), "first version") { | |
| 150 | + t.Fatal("first read") | |
| 151 | + } | |
| 152 | + if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte("second version"), 0o644); err != nil { | |
| 153 | + t.Fatal(err) | |
| 154 | + } | |
| 155 | + if got := System("base", dir); !strings.Contains(got, "second version") || strings.Contains(got, "first version") { | |
| 156 | + t.Errorf("after the edit:\n%s", got) | |
| 157 | + } | |
| 158 | +} | |
| 159 | + | |
| 160 | +func TestTokensEstimate(t *testing.T) { | |
| 161 | + f := File{Text: strings.Repeat("a", 700)} | |
| 162 | + if f.Tokens() != 200 { | |
| 163 | + t.Errorf("Tokens = %d, want 200 (700 characters / 3.5)", f.Tokens()) | |
| 164 | + } | |
| 165 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,165 @@ | |||
| 1 | +package agentsmd | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "os" | ||
| 5 | + "path/filepath" | ||
| 6 | + "strings" | ||
| 7 | + "testing" | ||
| 8 | + | ||
| 9 | + "mm/internal/config" | ||
| 10 | +) | ||
| 11 | + | ||
| 12 | +// withSettings sets config.Cfg.AgentsMd (and clears OutputSchema) for one test. | ||
| 13 | +func withSettings(t *testing.T, s config.AgentsMdConfig) { | ||
| 14 | + t.Helper() | ||
| 15 | + old, oldSchema := config.Cfg.AgentsMd, config.Cfg.OutputSchema | ||
| 16 | + config.Cfg.AgentsMd, config.Cfg.OutputSchema = s, nil | ||
| 17 | + t.Cleanup(func() { config.Cfg.AgentsMd, config.Cfg.OutputSchema = old, oldSchema }) | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +// project returns a directory holding an AGENTS.md with text. | ||
| 21 | +func project(t *testing.T, text string) string { | ||
| 22 | + t.Helper() | ||
| 23 | + dir := t.TempDir() | ||
| 24 | + if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte(text), 0o644); err != nil { | ||
| 25 | + t.Fatal(err) | ||
| 26 | + } | ||
| 27 | + return dir | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +var defaults = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | ||
| 31 | + | ||
| 32 | +func TestTheFileJoinsTheSystemPromptUnderAHeading(t *testing.T) { | ||
| 33 | + withSettings(t, defaults) | ||
| 34 | + dir := project(t, "# Build\nRun `make test` before committing.\n") | ||
| 35 | + | ||
| 36 | + got := System("You are Bob.\n", dir) | ||
| 37 | + | ||
| 38 | + want := "You are Bob.\n\nPROJECT INSTRUCTIONS (from " + filepath.Join(dir, "AGENTS.md") + ")\n" | ||
| 39 | + if !strings.HasPrefix(got, want) { | ||
| 40 | + t.Errorf("system prompt:\n%s\nwant it to start with:\n%s", got, want) | ||
| 41 | + } | ||
| 42 | + if !strings.HasSuffix(got, "# Build\nRun `make test` before committing.\n") { | ||
| 43 | + t.Errorf("the file's text is not at the end:\n%s", got) | ||
| 44 | + } | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +func TestNoFileLeavesThePromptUnchanged(t *testing.T) { | ||
| 48 | + withSettings(t, defaults) | ||
| 49 | + if got := System("You are Bob.", t.TempDir()); got != "You are Bob." { | ||
| 50 | + t.Errorf("System = %q, want the base prompt", got) | ||
| 51 | + } | ||
| 52 | + if got := System("You are Bob.", project(t, " \n\n ")); got != "You are Bob." { | ||
| 53 | + t.Errorf("a blank file: System = %q, want the base prompt", got) | ||
| 54 | + } | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +func TestDisabledReadsNothing(t *testing.T) { | ||
| 58 | + withSettings(t, config.AgentsMdConfig{Enabled: false, Path: "AGENTS.md"}) | ||
| 59 | + dir := project(t, "rules") | ||
| 60 | + if f := Load(dir); f.Status != Disabled || f.Path != "" { | ||
| 61 | + t.Errorf("Load = %+v, want Disabled", f) | ||
| 62 | + } | ||
| 63 | + if got := System("base", dir); got != "base" { | ||
| 64 | + t.Errorf("System = %q, want the base prompt", got) | ||
| 65 | + } | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +// The path is relative to the working directory, not to the process's. | ||
| 69 | +func TestThePathIsRelativeToTheWorkingDirectory(t *testing.T) { | ||
| 70 | + withSettings(t, config.AgentsMdConfig{Enabled: true, Path: "docs/AGENTS.md"}) | ||
| 71 | + dir := t.TempDir() | ||
| 72 | + if err := os.MkdirAll(filepath.Join(dir, "docs"), 0o755); err != nil { | ||
| 73 | + t.Fatal(err) | ||
| 74 | + } | ||
| 75 | + if err := os.WriteFile(filepath.Join(dir, "docs", "AGENTS.md"), []byte("docs rules"), 0o644); err != nil { | ||
| 76 | + t.Fatal(err) | ||
| 77 | + } | ||
| 78 | + f := Load(dir) | ||
| 79 | + if f.Status != Loaded || f.Path != filepath.Join(dir, "docs", "AGENTS.md") || f.Text != "docs rules" { | ||
| 80 | + t.Errorf("Load = %+v", f) | ||
| 81 | + } | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +func TestAnAbsolutePathIsUsedAsIs(t *testing.T) { | ||
| 85 | + dir := project(t, "absolute rules") | ||
| 86 | + withSettings(t, config.AgentsMdConfig{Enabled: true, Path: filepath.Join(dir, "AGENTS.md")}) | ||
| 87 | + if f := Load(t.TempDir()); f.Status != Loaded || f.Text != "absolute rules" { | ||
| 88 | + t.Errorf("Load = %+v", f) | ||
| 89 | + } | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +// Beyond maxChars the file is left out whole, never truncated. | ||
| 93 | +func TestAFileOverTheLimitIsSkippedWhole(t *testing.T) { | ||
| 94 | + withSettings(t, config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md", MaxChars: 10}) | ||
| 95 | + dir := project(t, "éééééééééééé") // 12 characters, 24 bytes | ||
| 96 | + | ||
| 97 | + f := Load(dir) | ||
| 98 | + if f.Status != Skipped || f.Chars() != 12 { | ||
| 99 | + t.Errorf("Load = %+v (chars %d), want Skipped with 12 characters", f, f.Chars()) | ||
| 100 | + } | ||
| 101 | + if got := System("base", dir); got != "base" { | ||
| 102 | + t.Errorf("System = %q, want the base prompt untouched", got) | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + withSettings(t, config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md", MaxChars: 12}) | ||
| 106 | + if f := Load(dir); f.Status != Loaded { | ||
| 107 | + t.Errorf("at the limit: Load = %+v, want Loaded", f) | ||
| 108 | + } | ||
| 109 | +} | ||
| 110 | + | ||
| 111 | +// A structured agent answers in JSON: it never reads prose instructions, | ||
| 112 | +// but Load still reports the file so the banner can say it was ignored. | ||
| 113 | +func TestAStructuredAgentIgnoresTheFile(t *testing.T) { | ||
| 114 | + withSettings(t, defaults) | ||
| 115 | + config.Cfg.OutputSchema = map[string]any{"type": "object"} | ||
| 116 | + dir := project(t, "Answer in prose.") | ||
| 117 | + | ||
| 118 | + if f := Load(dir); f.Status != Ignored { | ||
| 119 | + t.Errorf("Load = %+v, want Ignored", f) | ||
| 120 | + } | ||
| 121 | + if got := System("base", dir); got != "base" { | ||
| 122 | + t.Errorf("System = %q, want the base prompt", got) | ||
| 123 | + } | ||
| 124 | + if f := Load(t.TempDir()); f.Status != Missing { | ||
| 125 | + t.Errorf("no file: Load = %+v, want Missing (nothing to mention)", f) | ||
| 126 | + } | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +func TestAnUnreadableFileIsReported(t *testing.T) { | ||
| 130 | + withSettings(t, defaults) | ||
| 131 | + dir := t.TempDir() | ||
| 132 | + // A directory where the file should be: os.ReadFile fails, not ErrNotExist. | ||
| 133 | + if err := os.Mkdir(filepath.Join(dir, "AGENTS.md"), 0o755); err != nil { | ||
| 134 | + t.Fatal(err) | ||
| 135 | + } | ||
| 136 | + if f := Load(dir); f.Status != Unreadable || f.Err == nil { | ||
| 137 | + t.Errorf("Load = %+v, want Unreadable with its error", f) | ||
| 138 | + } | ||
| 139 | + if got := System("base", dir); got != "base" { | ||
| 140 | + t.Errorf("System = %q, want the base prompt", got) | ||
| 141 | + } | ||
| 142 | +} | ||
| 143 | + | ||
| 144 | +// The file is read on every call: an edited file is picked up by the next | ||
| 145 | +// conversation. | ||
| 146 | +func TestAnEditedFileIsPickedUpByTheNextCall(t *testing.T) { | ||
| 147 | + withSettings(t, defaults) | ||
| 148 | + dir := project(t, "first version") | ||
| 149 | + if !strings.Contains(System("base", dir), "first version") { | ||
| 150 | + t.Fatal("first read") | ||
| 151 | + } | ||
| 152 | + if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte("second version"), 0o644); err != nil { | ||
| 153 | + t.Fatal(err) | ||
| 154 | + } | ||
| 155 | + if got := System("base", dir); !strings.Contains(got, "second version") || strings.Contains(got, "first version") { | ||
| 156 | + t.Errorf("after the edit:\n%s", got) | ||
| 157 | + } | ||
| 158 | +} | ||
| 159 | + | ||
| 160 | +func TestTokensEstimate(t *testing.T) { | ||
| 161 | + f := File{Text: strings.Repeat("a", 700)} | ||
| 162 | + if f.Tokens() != 200 { | ||
| 163 | + t.Errorf("Tokens = %d, want 200 (700 characters / 3.5)", f.Tokens()) | ||
| 164 | + } | ||
| 165 | +} | ||
added
internal/agentsmd/example_test.go +29 -0 | new file mode 100644 | ||
| @@ -0,0 +1,29 @@ | ||
| 1 | +package agentsmd_test | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "fmt" | |
| 5 | + "os" | |
| 6 | + "path/filepath" | |
| 7 | + "strings" | |
| 8 | + | |
| 9 | + "mm/internal/agentsmd" | |
| 10 | + "mm/internal/config" | |
| 11 | +) | |
| 12 | + | |
| 13 | +func ExampleSystem() { | |
| 14 | + dir, _ := os.MkdirTemp("", "project") | |
| 15 | + defer os.RemoveAll(dir) | |
| 16 | + _ = os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte("Run `make test` before committing."), 0o644) | |
| 17 | + config.Cfg.AgentsMd = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | |
| 18 | + | |
| 19 | + system := agentsmd.System("You are Bob.", dir) | |
| 20 | + | |
| 21 | + fmt.Println(strings.Replace(system, dir, "<project>", 1)) | |
| 22 | + // Output: | |
| 23 | + // You are Bob. | |
| 24 | + // | |
| 25 | + // PROJECT INSTRUCTIONS (from <project>/AGENTS.md) | |
| 26 | + // The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins. | |
| 27 | + // | |
| 28 | + // Run `make test` before committing. | |
| 29 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | +package agentsmd_test | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "fmt" | ||
| 5 | + "os" | ||
| 6 | + "path/filepath" | ||
| 7 | + "strings" | ||
| 8 | + | ||
| 9 | + "mm/internal/agentsmd" | ||
| 10 | + "mm/internal/config" | ||
| 11 | +) | ||
| 12 | + | ||
| 13 | +func ExampleSystem() { | ||
| 14 | + dir, _ := os.MkdirTemp("", "project") | ||
| 15 | + defer os.RemoveAll(dir) | ||
| 16 | + _ = os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte("Run `make test` before committing."), 0o644) | ||
| 17 | + config.Cfg.AgentsMd = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | ||
| 18 | + | ||
| 19 | + system := agentsmd.System("You are Bob.", dir) | ||
| 20 | + | ||
| 21 | + fmt.Println(strings.Replace(system, dir, "<project>", 1)) | ||
| 22 | + // Output: | ||
| 23 | + // You are Bob. | ||
| 24 | + // | ||
| 25 | + // PROJECT INSTRUCTIONS (from <project>/AGENTS.md) | ||
| 26 | + // The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins. | ||
| 27 | + // | ||
| 28 | + // Run `make test` before committing. | ||
| 29 | +} | ||
added
internal/config/agentsmd_test.go +46 -0 | new file mode 100644 | ||
| @@ -0,0 +1,46 @@ | ||
| 1 | +package config | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "strings" | |
| 5 | + "testing" | |
| 6 | +) | |
| 7 | + | |
| 8 | +// On by default, reading AGENTS.md, with no size limit: a YAML that never | |
| 9 | +// mentions the block reads the project's file. | |
| 10 | +func TestAgentsMdDefaults(t *testing.T) { | |
| 11 | + if err := loadYAML(t, "model: m\n"); err != nil { | |
| 12 | + t.Fatal(err) | |
| 13 | + } | |
| 14 | + if got := Cfg.AgentsMd; !got.Enabled || got.Path != "AGENTS.md" || got.MaxChars != 0 { | |
| 15 | + t.Errorf("defaults: %+v, want enabled, AGENTS.md, no limit", got) | |
| 16 | + } | |
| 17 | +} | |
| 18 | + | |
| 19 | +func TestAgentsMdIsRead(t *testing.T) { | |
| 20 | + if err := loadYAML(t, "model: m\nagentsMd:\n enabled: false\n path: docs/AGENTS.md\n maxChars: 8000\n"); err != nil { | |
| 21 | + t.Fatal(err) | |
| 22 | + } | |
| 23 | + if got := Cfg.AgentsMd; got.Enabled || got.Path != "docs/AGENTS.md" || got.MaxChars != 8000 { | |
| 24 | + t.Errorf("after the file: %+v", got) | |
| 25 | + } | |
| 26 | +} | |
| 27 | + | |
| 28 | +func TestAgentsMdValidation(t *testing.T) { | |
| 29 | + cases := map[string]string{ | |
| 30 | + "model: m\nagentsMd:\n path: \"\"\n": "agentsMd.path must not be empty", | |
| 31 | + "model: m\nagentsMd:\n maxChars: -1\n": "agentsMd.maxChars must be >= 0", | |
| 32 | + } | |
| 33 | + for body, want := range cases { | |
| 34 | + t.Run(want, func(t *testing.T) { | |
| 35 | + if err := loadYAML(t, body); err == nil || !strings.Contains(err.Error(), want) { | |
| 36 | + t.Errorf("%q: Load = %v, want %q", body, err, want) | |
| 37 | + } | |
| 38 | + }) | |
| 39 | + } | |
| 40 | + // Off, an empty path is no error: nothing will be read. | |
| 41 | + t.Run("disabled", func(t *testing.T) { | |
| 42 | + if err := loadYAML(t, "model: m\nagentsMd:\n enabled: false\n path: \"\"\n"); err != nil { | |
| 43 | + t.Errorf("disabled with an empty path: %v", err) | |
| 44 | + } | |
| 45 | + }) | |
| 46 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | +package config | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "strings" | ||
| 5 | + "testing" | ||
| 6 | +) | ||
| 7 | + | ||
| 8 | +// On by default, reading AGENTS.md, with no size limit: a YAML that never | ||
| 9 | +// mentions the block reads the project's file. | ||
| 10 | +func TestAgentsMdDefaults(t *testing.T) { | ||
| 11 | + if err := loadYAML(t, "model: m\n"); err != nil { | ||
| 12 | + t.Fatal(err) | ||
| 13 | + } | ||
| 14 | + if got := Cfg.AgentsMd; !got.Enabled || got.Path != "AGENTS.md" || got.MaxChars != 0 { | ||
| 15 | + t.Errorf("defaults: %+v, want enabled, AGENTS.md, no limit", got) | ||
| 16 | + } | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +func TestAgentsMdIsRead(t *testing.T) { | ||
| 20 | + if err := loadYAML(t, "model: m\nagentsMd:\n enabled: false\n path: docs/AGENTS.md\n maxChars: 8000\n"); err != nil { | ||
| 21 | + t.Fatal(err) | ||
| 22 | + } | ||
| 23 | + if got := Cfg.AgentsMd; got.Enabled || got.Path != "docs/AGENTS.md" || got.MaxChars != 8000 { | ||
| 24 | + t.Errorf("after the file: %+v", got) | ||
| 25 | + } | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +func TestAgentsMdValidation(t *testing.T) { | ||
| 29 | + cases := map[string]string{ | ||
| 30 | + "model: m\nagentsMd:\n path: \"\"\n": "agentsMd.path must not be empty", | ||
| 31 | + "model: m\nagentsMd:\n maxChars: -1\n": "agentsMd.maxChars must be >= 0", | ||
| 32 | + } | ||
| 33 | + for body, want := range cases { | ||
| 34 | + t.Run(want, func(t *testing.T) { | ||
| 35 | + if err := loadYAML(t, body); err == nil || !strings.Contains(err.Error(), want) { | ||
| 36 | + t.Errorf("%q: Load = %v, want %q", body, err, want) | ||
| 37 | + } | ||
| 38 | + }) | ||
| 39 | + } | ||
| 40 | + // Off, an empty path is no error: nothing will be read. | ||
| 41 | + t.Run("disabled", func(t *testing.T) { | ||
| 42 | + if err := loadYAML(t, "model: m\nagentsMd:\n enabled: false\n path: \"\"\n"); err != nil { | ||
| 43 | + t.Errorf("disabled with an empty path: %v", err) | ||
| 44 | + } | ||
| 45 | + }) | ||
| 46 | +} | ||
modified
internal/config/config.go +28 -0 | @@ -155,6 +155,11 @@ type Config struct { | ||
| 155 | 155 | // unless `trace.enabled: true` or AGENT_TRACE=true. |
| 156 | 156 | Trace TraceConfig `yaml:"trace"` |
| 157 | 157 | |
| 158 | + // AgentsMd: the project's AGENTS.md (https://agents.md), appended to the | |
| 159 | + // system prompt of every conversation started in a directory that has | |
| 160 | + // one (internal/agentsmd, ticket 81). On by default. | |
| 161 | + AgentsMd AgentsMdConfig `yaml:"agentsMd"` | |
| 162 | + | |
| 158 | 163 | // ACP: how the agent introduces itself to an editor in the `initialize` |
| 159 | 164 | // response (`mm -acp`). The version is not configurable: it is the |
| 160 | 165 | // binary's own, the one `mm --version` prints. |
| @@ -400,6 +405,21 @@ type TraceConfig struct { | ||
| 400 | 405 | Dir string `yaml:"dir"` |
| 401 | 406 | } |
| 402 | 407 | |
| 408 | +// AgentsMdConfig: whether and where the project instructions are read. | |
| 409 | +type AgentsMdConfig struct { | |
| 410 | + // Enabled reads the file at the start of every conversation (start-up, | |
| 411 | + // /new, /resume, a new or reloaded ACP session). An agent asked by | |
| 412 | + // another one reads it only when ITS OWN config says so. | |
| 413 | + Enabled bool `yaml:"enabled"` | |
| 414 | + // Path: the file, relative to the conversation's working directory (the | |
| 415 | + // session's cwd over ACP) — not to the config file: one agent.yaml | |
| 416 | + // serves many projects. An absolute path is used as-is. | |
| 417 | + Path string `yaml:"path"` | |
| 418 | + // MaxChars: beyond this many characters the file is SKIPPED, with a | |
| 419 | + // banner warning — never truncated. 0 = no limit. | |
| 420 | + MaxChars int `yaml:"maxChars"` | |
| 421 | +} | |
| 422 | + | |
| 403 | 423 | type SessionsConfig struct { |
| 404 | 424 | // Enabled turns the persistence on. Without it nothing is written and |
| 405 | 425 | // ACP does not advertise session/load. |
| @@ -515,6 +535,10 @@ which ones you left running.`, | ||
| 515 | 535 | Enabled: false, |
| 516 | 536 | Dir: ".mm/traces", |
| 517 | 537 | }, |
| 538 | + AgentsMd: AgentsMdConfig{ | |
| 539 | + Enabled: true, | |
| 540 | + Path: "AGENTS.md", | |
| 541 | + }, | |
| 518 | 542 | ACP: ACPConfig{ |
| 519 | 543 | Name: "bob", |
| 520 | 544 | Title: DefaultACPTitle, |
| @@ -731,6 +755,10 @@ func (c Config) validate() error { | ||
| 731 | 755 | return errors.New("sessions.dir must not be empty") |
| 732 | 756 | case c.Trace.Enabled && strings.TrimSpace(c.Trace.Dir) == "": |
| 733 | 757 | return errors.New("trace.dir must not be empty") |
| 758 | + case c.AgentsMd.Enabled && strings.TrimSpace(c.AgentsMd.Path) == "": | |
| 759 | + return errors.New("agentsMd.path must not be empty") | |
| 760 | + case c.AgentsMd.MaxChars < 0: | |
| 761 | + return errors.New("agentsMd.maxChars must be >= 0") | |
| 734 | 762 | case strings.TrimSpace(c.ACP.Name) == "": |
| 735 | 763 | return errors.New("acp.name must not be empty") |
| 736 | 764 | case c.Permissions != "" && c.Permissions != PermissionsAuto && c.Permissions != PermissionsAsk: |
| @@ -155,6 +155,11 @@ type Config struct { | |||
| 155 | // unless `trace.enabled: true` or AGENT_TRACE=true. | 155 | // unless `trace.enabled: true` or AGENT_TRACE=true. |
| 156 | Trace TraceConfig `yaml:"trace"` | 156 | Trace TraceConfig `yaml:"trace"` |
| 157 | 157 | ||
| 158 | + // AgentsMd: the project's AGENTS.md (https://agents.md), appended to the | ||
| 159 | + // system prompt of every conversation started in a directory that has | ||
| 160 | + // one (internal/agentsmd, ticket 81). On by default. | ||
| 161 | + AgentsMd AgentsMdConfig `yaml:"agentsMd"` | ||
| 162 | + | ||
| 158 | // ACP: how the agent introduces itself to an editor in the `initialize` | 163 | // ACP: how the agent introduces itself to an editor in the `initialize` |
| 159 | // response (`mm -acp`). The version is not configurable: it is the | 164 | // response (`mm -acp`). The version is not configurable: it is the |
| 160 | // binary's own, the one `mm --version` prints. | 165 | // binary's own, the one `mm --version` prints. |
| @@ -400,6 +405,21 @@ type TraceConfig struct { | |||
| 400 | Dir string `yaml:"dir"` | 405 | Dir string `yaml:"dir"` |
| 401 | } | 406 | } |
| 402 | 407 | ||
| 408 | +// AgentsMdConfig: whether and where the project instructions are read. | ||
| 409 | +type AgentsMdConfig struct { | ||
| 410 | + // Enabled reads the file at the start of every conversation (start-up, | ||
| 411 | + // /new, /resume, a new or reloaded ACP session). An agent asked by | ||
| 412 | + // another one reads it only when ITS OWN config says so. | ||
| 413 | + Enabled bool `yaml:"enabled"` | ||
| 414 | + // Path: the file, relative to the conversation's working directory (the | ||
| 415 | + // session's cwd over ACP) — not to the config file: one agent.yaml | ||
| 416 | + // serves many projects. An absolute path is used as-is. | ||
| 417 | + Path string `yaml:"path"` | ||
| 418 | + // MaxChars: beyond this many characters the file is SKIPPED, with a | ||
| 419 | + // banner warning — never truncated. 0 = no limit. | ||
| 420 | + MaxChars int `yaml:"maxChars"` | ||
| 421 | +} | ||
| 422 | + | ||
| 403 | type SessionsConfig struct { | 423 | type SessionsConfig struct { |
| 404 | // Enabled turns the persistence on. Without it nothing is written and | 424 | // Enabled turns the persistence on. Without it nothing is written and |
| 405 | // ACP does not advertise session/load. | 425 | // ACP does not advertise session/load. |
| @@ -515,6 +535,10 @@ which ones you left running.`, | |||
| 515 | Enabled: false, | 535 | Enabled: false, |
| 516 | Dir: ".mm/traces", | 536 | Dir: ".mm/traces", |
| 517 | }, | 537 | }, |
| 538 | + AgentsMd: AgentsMdConfig{ | ||
| 539 | + Enabled: true, | ||
| 540 | + Path: "AGENTS.md", | ||
| 541 | + }, | ||
| 518 | ACP: ACPConfig{ | 542 | ACP: ACPConfig{ |
| 519 | Name: "bob", | 543 | Name: "bob", |
| 520 | Title: DefaultACPTitle, | 544 | Title: DefaultACPTitle, |
| @@ -731,6 +755,10 @@ func (c Config) validate() error { | |||
| 731 | return errors.New("sessions.dir must not be empty") | 755 | return errors.New("sessions.dir must not be empty") |
| 732 | case c.Trace.Enabled && strings.TrimSpace(c.Trace.Dir) == "": | 756 | case c.Trace.Enabled && strings.TrimSpace(c.Trace.Dir) == "": |
| 733 | return errors.New("trace.dir must not be empty") | 757 | return errors.New("trace.dir must not be empty") |
| 758 | + case c.AgentsMd.Enabled && strings.TrimSpace(c.AgentsMd.Path) == "": | ||
| 759 | + return errors.New("agentsMd.path must not be empty") | ||
| 760 | + case c.AgentsMd.MaxChars < 0: | ||
| 761 | + return errors.New("agentsMd.maxChars must be >= 0") | ||
| 734 | case strings.TrimSpace(c.ACP.Name) == "": | 762 | case strings.TrimSpace(c.ACP.Name) == "": |
| 735 | return errors.New("acp.name must not be empty") | 763 | return errors.New("acp.name must not be empty") |
| 736 | case c.Permissions != "" && c.Permissions != PermissionsAuto && c.Permissions != PermissionsAsk: | 764 | case c.Permissions != "" && c.Permissions != PermissionsAuto && c.Permissions != PermissionsAsk: |
added
internal/tui/agentsmd_test.go +37 -0 | new file mode 100644 | ||
| @@ -0,0 +1,37 @@ | ||
| 1 | +package tui | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "os" | |
| 5 | + "path/filepath" | |
| 6 | + "strings" | |
| 7 | + "testing" | |
| 8 | + | |
| 9 | + "mm/internal/config" | |
| 10 | +) | |
| 11 | + | |
| 12 | +// The window reads the AGENTS.md of its working directory when it opens, and | |
| 13 | +// again on /new. | |
| 14 | +func TestTheWindowReadsTheAgentsMdAtStartAndOnNew(t *testing.T) { | |
| 15 | + r, _ := newTestRunner(t, &fakeModel{}) | |
| 16 | + config.Cfg.AgentsMd = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | |
| 17 | + write := func(text string) { | |
| 18 | + if err := os.WriteFile(filepath.Join(r.opts.Cwd, "AGENTS.md"), []byte(text), 0o644); err != nil { | |
| 19 | + t.Fatal(err) | |
| 20 | + } | |
| 21 | + } | |
| 22 | + // The runner was built before the file existed: no section. | |
| 23 | + if got := r.messages[0].Text(); got != "sys" { | |
| 24 | + t.Fatalf("system before any AGENTS.md: %q", got) | |
| 25 | + } | |
| 26 | + | |
| 27 | + write("window rules") | |
| 28 | + r.newSession() | |
| 29 | + if got := r.messages[0].Text(); !strings.HasPrefix(got, "sys\n\nPROJECT INSTRUCTIONS (from "+filepath.Join(r.opts.Cwd, "AGENTS.md")+")") || !strings.Contains(got, "window rules") { | |
| 30 | + t.Errorf("system after /new:\n%s", got) | |
| 31 | + } | |
| 32 | + | |
| 33 | + r2 := newRunner(r.opts, &Transcript{}, func() {}) | |
| 34 | + if got := r2.messages[0].Text(); !strings.Contains(got, "window rules") { | |
| 35 | + t.Errorf("system of a window opened in the project:\n%s", got) | |
| 36 | + } | |
| 37 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | +package tui | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "os" | ||
| 5 | + "path/filepath" | ||
| 6 | + "strings" | ||
| 7 | + "testing" | ||
| 8 | + | ||
| 9 | + "mm/internal/config" | ||
| 10 | +) | ||
| 11 | + | ||
| 12 | +// The window reads the AGENTS.md of its working directory when it opens, and | ||
| 13 | +// again on /new. | ||
| 14 | +func TestTheWindowReadsTheAgentsMdAtStartAndOnNew(t *testing.T) { | ||
| 15 | + r, _ := newTestRunner(t, &fakeModel{}) | ||
| 16 | + config.Cfg.AgentsMd = config.AgentsMdConfig{Enabled: true, Path: "AGENTS.md"} | ||
| 17 | + write := func(text string) { | ||
| 18 | + if err := os.WriteFile(filepath.Join(r.opts.Cwd, "AGENTS.md"), []byte(text), 0o644); err != nil { | ||
| 19 | + t.Fatal(err) | ||
| 20 | + } | ||
| 21 | + } | ||
| 22 | + // The runner was built before the file existed: no section. | ||
| 23 | + if got := r.messages[0].Text(); got != "sys" { | ||
| 24 | + t.Fatalf("system before any AGENTS.md: %q", got) | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + write("window rules") | ||
| 28 | + r.newSession() | ||
| 29 | + if got := r.messages[0].Text(); !strings.HasPrefix(got, "sys\n\nPROJECT INSTRUCTIONS (from "+filepath.Join(r.opts.Cwd, "AGENTS.md")+")") || !strings.Contains(got, "window rules") { | ||
| 30 | + t.Errorf("system after /new:\n%s", got) | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + r2 := newRunner(r.opts, &Transcript{}, func() {}) | ||
| 34 | + if got := r2.messages[0].Text(); !strings.Contains(got, "window rules") { | ||
| 35 | + t.Errorf("system of a window opened in the project:\n%s", got) | ||
| 36 | + } | ||
| 37 | +} | ||
modified
internal/tui/commands.go +3 -2 | @@ -7,6 +7,7 @@ package tui | ||
| 7 | 7 | import ( |
| 8 | 8 | "fmt" |
| 9 | 9 | |
| 10 | + "mm/internal/agentsmd" | |
| 10 | 11 | "mm/internal/command" |
| 11 | 12 | "mm/internal/session" |
| 12 | 13 | "mm/internal/store" |
| @@ -135,7 +136,7 @@ func (r *runner) cancelTurn() bool { | ||
| 135 | 136 | // newSession is /new: the same three resets as the REPL's startNewSession. |
| 136 | 137 | func (r *runner) newSession() { |
| 137 | 138 | forgotten := session.Forgotten(r.messages) |
| 138 | - r.messages = session.Fresh(r.opts.System) | |
| 139 | + r.messages = session.Fresh(agentsmd.System(r.opts.System, r.opts.Cwd)) | |
| 139 | 140 | r.opts.Model.ForgetInputTokens() |
| 140 | 141 | r.opts.Detector.Reset() |
| 141 | 142 | r.keep.Renew() |
| @@ -144,7 +145,7 @@ func (r *runner) newSession() { | ||
| 144 | 145 | |
| 145 | 146 | // resume is /resume [id]: a stored session becomes the current one. |
| 146 | 147 | func (r *runner) resume(id string) { |
| 147 | - msgs, line, err := r.keep.Resume(id, r.opts.System) | |
| 148 | + msgs, line, err := r.keep.Resume(id, agentsmd.System(r.opts.System, r.opts.Cwd)) | |
| 148 | 149 | if err != nil { |
| 149 | 150 | r.t.Error("⚠️ " + err.Error()) |
| 150 | 151 | r.record(store.NewFailure(r.messages, store.FailResume, err.Error(), err)) |
| @@ -7,6 +7,7 @@ package tui | |||
| 7 | import ( | 7 | import ( |
| 8 | "fmt" | 8 | "fmt" |
| 9 | 9 | ||
| 10 | + "mm/internal/agentsmd" | ||
| 10 | "mm/internal/command" | 11 | "mm/internal/command" |
| 11 | "mm/internal/session" | 12 | "mm/internal/session" |
| 12 | "mm/internal/store" | 13 | "mm/internal/store" |
| @@ -135,7 +136,7 @@ func (r *runner) cancelTurn() bool { | |||
| 135 | // newSession is /new: the same three resets as the REPL's startNewSession. | 136 | // newSession is /new: the same three resets as the REPL's startNewSession. |
| 136 | func (r *runner) newSession() { | 137 | func (r *runner) newSession() { |
| 137 | forgotten := session.Forgotten(r.messages) | 138 | forgotten := session.Forgotten(r.messages) |
| 138 | - r.messages = session.Fresh(r.opts.System) | 139 | + r.messages = session.Fresh(agentsmd.System(r.opts.System, r.opts.Cwd)) |
| 139 | r.opts.Model.ForgetInputTokens() | 140 | r.opts.Model.ForgetInputTokens() |
| 140 | r.opts.Detector.Reset() | 141 | r.opts.Detector.Reset() |
| 141 | r.keep.Renew() | 142 | r.keep.Renew() |
| @@ -144,7 +145,7 @@ func (r *runner) newSession() { | |||
| 144 | 145 | ||
| 145 | // resume is /resume [id]: a stored session becomes the current one. | 146 | // resume is /resume [id]: a stored session becomes the current one. |
| 146 | func (r *runner) resume(id string) { | 147 | func (r *runner) resume(id string) { |
| 147 | - msgs, line, err := r.keep.Resume(id, r.opts.System) | 148 | + msgs, line, err := r.keep.Resume(id, agentsmd.System(r.opts.System, r.opts.Cwd)) |
| 148 | if err != nil { | 149 | if err != nil { |
| 149 | r.t.Error("⚠️ " + err.Error()) | 150 | r.t.Error("⚠️ " + err.Error()) |
| 150 | r.record(store.NewFailure(r.messages, store.FailResume, err.Error(), err)) | 151 | r.record(store.NewFailure(r.messages, store.FailResume, err.Error(), err)) |
modified
internal/tui/turn.go +2 -1 | @@ -7,6 +7,7 @@ import ( | ||
| 7 | 7 | "sync" |
| 8 | 8 | "sync/atomic" |
| 9 | 9 | |
| 10 | + "mm/internal/agentsmd" | |
| 10 | 11 | "mm/internal/ask" |
| 11 | 12 | "mm/internal/compact" |
| 12 | 13 | "mm/internal/config" |
| @@ -130,7 +131,7 @@ func newRunner(opts Options, t *Transcript, wake func()) *runner { | ||
| 130 | 131 | t: t, |
| 131 | 132 | wake: wake, |
| 132 | 133 | keep: store.NewKeeper(opts.Cwd), |
| 133 | - messages: session.Fresh(opts.System), | |
| 134 | + messages: session.Fresh(agentsmd.System(opts.System, opts.Cwd)), | |
| 134 | 135 | results: make(chan outcome, 1), |
| 135 | 136 | } |
| 136 | 137 | r.busy.Store("") |
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | "sync" | 7 | "sync" |
| 8 | "sync/atomic" | 8 | "sync/atomic" |
| 9 | 9 | ||
| 10 | + "mm/internal/agentsmd" | ||
| 10 | "mm/internal/ask" | 11 | "mm/internal/ask" |
| 11 | "mm/internal/compact" | 12 | "mm/internal/compact" |
| 12 | "mm/internal/config" | 13 | "mm/internal/config" |
| @@ -130,7 +131,7 @@ func newRunner(opts Options, t *Transcript, wake func()) *runner { | |||
| 130 | t: t, | 131 | t: t, |
| 131 | wake: wake, | 132 | wake: wake, |
| 132 | keep: store.NewKeeper(opts.Cwd), | 133 | keep: store.NewKeeper(opts.Cwd), |
| 133 | - messages: session.Fresh(opts.System), | 134 | + messages: session.Fresh(agentsmd.System(opts.System, opts.Cwd)), |
| 134 | results: make(chan outcome, 1), | 135 | results: make(chan outcome, 1), |
| 135 | } | 136 | } |
| 136 | r.busy.Store("") | 137 | r.busy.Store("") |
modified
main.go +4 -2 | @@ -528,6 +528,8 @@ func printBanner(ctx context.Context, e *engine.Engine, path string, set toolSet | ||
| 528 | 528 | // An agent with a structuredOutput has neither tools nor skills on |
| 529 | 529 | // purpose (config.StructuredOutputConflict): nothing to warn about. |
| 530 | 530 | structuredAgent := config.Cfg.OutputSchema != nil |
| 531 | + agentsMdCol, agentsMdWarnings := agentsMdBanner(acpMode, info.ContextWindow) | |
| 532 | + info.Warnings = append(info.Warnings, agentsMdWarnings...) | |
| 531 | 533 | if skillCount == 0 && !structuredAgent { |
| 532 | 534 | info.Warnings = append(info.Warnings, |
| 533 | 535 | fmt.Sprintf("no skills found in %s (expected <name>.md or <name>/SKILL.md there)", config.Cfg.SkillsDir)) |
| @@ -582,8 +584,8 @@ func printBanner(ctx context.Context, e *engine.Engine, path string, set toolSet | ||
| 582 | 584 | // The version leads the line: it is the first thing anyone is asked for |
| 583 | 585 | // when something behaves oddly, and the banner is the one output every |
| 584 | 586 | // front end shows — the window keeps it as the conversation's first line. |
| 585 | - say("%smm %s | config: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d | %s%s%s", | |
| 586 | - dim, version.Short(), path, e.Backend.Provider, config.Cfg.Model, ctxCol, toolCol, skillCount, yoloColumn(), traceColumn(acpMode), off) | |
| 587 | + say("%smm %s | config: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d | %s%s%s%s", | |
| 588 | + dim, version.Short(), path, e.Backend.Provider, config.Cfg.Model, ctxCol, toolCol, skillCount, yoloColumn(), agentsMdCol, traceColumn(acpMode), off) | |
| 587 | 589 | } |
| 588 | 590 | |
| 589 | 591 | // yoloColumn says in the banner whether the tools run unasked — the one |
| @@ -528,6 +528,8 @@ func printBanner(ctx context.Context, e *engine.Engine, path string, set toolSet | |||
| 528 | // An agent with a structuredOutput has neither tools nor skills on | 528 | // An agent with a structuredOutput has neither tools nor skills on |
| 529 | // purpose (config.StructuredOutputConflict): nothing to warn about. | 529 | // purpose (config.StructuredOutputConflict): nothing to warn about. |
| 530 | structuredAgent := config.Cfg.OutputSchema != nil | 530 | structuredAgent := config.Cfg.OutputSchema != nil |
| 531 | + agentsMdCol, agentsMdWarnings := agentsMdBanner(acpMode, info.ContextWindow) | ||
| 532 | + info.Warnings = append(info.Warnings, agentsMdWarnings...) | ||
| 531 | if skillCount == 0 && !structuredAgent { | 533 | if skillCount == 0 && !structuredAgent { |
| 532 | info.Warnings = append(info.Warnings, | 534 | info.Warnings = append(info.Warnings, |
| 533 | fmt.Sprintf("no skills found in %s (expected <name>.md or <name>/SKILL.md there)", config.Cfg.SkillsDir)) | 535 | fmt.Sprintf("no skills found in %s (expected <name>.md or <name>/SKILL.md there)", config.Cfg.SkillsDir)) |
| @@ -582,8 +584,8 @@ func printBanner(ctx context.Context, e *engine.Engine, path string, set toolSet | |||
| 582 | // The version leads the line: it is the first thing anyone is asked for | 584 | // The version leads the line: it is the first thing anyone is asked for |
| 583 | // when something behaves oddly, and the banner is the one output every | 585 | // when something behaves oddly, and the banner is the one output every |
| 584 | // front end shows — the window keeps it as the conversation's first line. | 586 | // front end shows — the window keeps it as the conversation's first line. |
| 585 | - say("%smm %s | config: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d | %s%s%s", | 587 | + say("%smm %s | config: %s | provider: %s | model: %s | ctx: %s | tools: %s | skills: %d | %s%s%s%s", |
| 586 | - dim, version.Short(), path, e.Backend.Provider, config.Cfg.Model, ctxCol, toolCol, skillCount, yoloColumn(), traceColumn(acpMode), off) | 588 | + dim, version.Short(), path, e.Backend.Provider, config.Cfg.Model, ctxCol, toolCol, skillCount, yoloColumn(), agentsMdCol, traceColumn(acpMode), off) |
| 587 | } | 589 | } |
| 588 | 590 | ||
| 589 | // yoloColumn says in the banner whether the tools run unasked — the one | 591 | // yoloColumn says in the banner whether the tools run unasked — the one |