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

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

Download ZIP
🛟 Updated. 05db870 · on feature/memory-search · k33g · 2d ago
configuration.md · 287 lines · 29.0 KBmarkdown
Blame HistoryOpen raw

Reference: configuration file

Neutral, exhaustive description of the YAML file read by mm. Every key is optional; a key left out keeps its built-in default. The file is decoded on top of the defaults, so unknown keys are ignored.

Top-level keys

Key Type Default Description
provider string dmr Registry key of the LLM server: dmr, llamacpp, llmman or ollama. Overridden by AGENT_PROVIDER.
model string huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M Model identifier in the provider's naming. Overridden by AGENT_MODEL.
baseUrl string "" (provider default) OpenAI-compatible endpoint tried first. Overridden by AGENT_BASE_URL.
fallback string or absent absent (provider default) Endpoint used when baseUrl does not answer /models within 2 s. Absent = provider default; "" = no fallback.
apiKeyEnv string "" (provider default) Name of the environment variable holding the API key.
contextWindow int 0 Tokens the server serves. 0 = ask the server, else unknown. Must be ≥ 0.
maxOutput int 16000 Maximum characters a tool returns to the model; longer output keeps its head and up to 2000 characters of tail. Must be > 0.
maxTurns int 10 Maximum model ↔ tools round trips per question. Must be > 0.
previewLines int 20 Lines of a command's output echoed to the terminal. 0 disables the echo. Must be ≥ 0.
displayCommands bool false Print the numbered list of commands and file operations after each answer.
displayHooks bool false Show every hook that ran: event, command, tool or agent, decision, size of the context added (never its text). After each answer in the line mode, -p and the window; as a finished tool call per hook in an editor. See hooks.
showThinking bool true Show what a thinking model reasons about before it answers. Only the display is optional: the reasoning is always read off the wire, because it is what keeps the watchdog alive while the model thinks. See Thinking models.
system string built-in prompt The system prompt, first message of every history.
structuredOutput string (Go declarations) "" The shape every answer must follow, as JSON: Go type declarations and one var naming the root of the answer. See structuredOutput below. Empty = free-text answers. Incompatible with any declared tool.
skillsDir string skills Directory holding the skills, as <name>.md files or <name>/SKILL.md directories. A relative path is resolved from the directory mm is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there.
bashTool bool true Declare bash. false removes it: the model can still read and edit files (editTools) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool.
editTools bool true Declare read_file, write_file, edit_file. false leaves bash and read_skill only.
fetchContentTool bool false Declare fetch_content, which fetches web pages as Markdown. Off by default: it is the only built-in tool that reaches the network.
askUserTool bool true Declare ask_user, through which the model asks the user questions (pick one, pick several, type a text, yes or no) and waits for the answers. false removes it: the model cannot ask. Never declared beside a structuredOutput. Overridden by AGENT_ASK_USER; for an agent asked by another one, also by the asker's mmConfigs.<name>.askUser. See tools.
saveAnswerTool bool false Declare save_answer, which writes an answer the model already gave to a new file: the model sends a file name, not the text again. Off by default. The /save command exists either way. See tools.
unloadModels bool true The /unload [model] command (terminal, window, editor) and the unload_model tool, which free a model from the server's memory: llmman and Ollama, llama-server in router mode. false removes both. The tool is never declared beside a structuredOutput. See tools and How to free a model's memory.
sampling map temperature: 0.0, top_p: 0.9, max_tokens: 4096 Generation settings, OpenAI API key names, passed to the server. A key the OpenAI API does not declare is sent as it is, at the top level of the request — for example chat_template_kwargs: {enable_thinking: false}, which tells a llama.cpp server (dmr, llamacpp, llmman) not to reason; reasoning_effort: none does the same on Ollama. messages, tools, tool_choice (and the legacy functions, function_call) are ignored: Genkit builds them from the conversation. The shipped agent.yaml adds parallel_tool_calls: false. max_tokens also bounds one tool call: a write_file of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with the model hit max_tokens (N) while writing a tool call; raise the value if that happens. With streaming, mm also asks the server for token counts (stream_options.include_usage).
watchdogTimeout duration 20s Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests.
firstTokenTimeout duration 3m Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than watchdogTimeout; must be >= 0. With a llama.cpp server (dmr, llamacpp, llmman — whose local GGUF models run in llama-server) mm asks for return_progress: each progress report restarts this clock, and the spinner shows Reading the prompt 12.0k/25.3k tokens. Ollama sends no progress: this one wait covers the whole prompt (and the model's loading). On a timeout the watchdog says no first token from the server after … (before the first token) or connection hang detected after … (after).
bashTimeout duration 30s How long one bash command may run before it is killed; the tool then answers [error: <bashTimeout> timeout exceeded]. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than watchdogTimeout ends the turn, so raise both together.
context map see below Automatic history compression.
sessions map see below Persistent, per-project sessions.
trace map see below Write every request to the model and every response to files.
agentsMd map see below Append the project's AGENTS.md to the system prompt of every conversation. On by default.
acp map see below How mm -acp introduces itself to the editor.
lsp map see below Language servers behind the lsp tool. No server listed = no tool.
hooks map event → list none Commands run before a question reaches the model (preGeneration), before and after each tool call (preToolCall, postToolCall) after the turn (postGeneration), and before and after each hand-off to another agent (preAsk, postAsk); each reads one JSON object on stdin and may answer one on stdout. Entry keys: command (required), matcher (tool and agent events only: a tool name, or an agent name), timeout (default 10s), onError (fail | warn | ignore, default warn). See hooks.
tools list [] Commands declared as typed tools: each entry a name, a description, its parameters and a command run without a shell. See tools keys below and declared tools.
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.
yolo bool true true: the tools run without asking. false: every call that changes something — bash, write_file, edit_file, fetch_content with output, lsp rename, plan create/update/next/delete — waits for the user's answer in every front end (line mode, window, editor), and so does every MCP tool, whatever mcp.permissions says. Answers: allow once, allow this tool for the session, reject this call, never (this tool, for the session). read_file, read_skill, fetch_content without output and the lsp navigation actions never ask. In -p and mm --ask, nobody can answer: those calls are rejected. An agent started by /ask, ask_agent or mm --ask runs with AGENT_YOLO=false and submits its calls to the asking agent: the yolo of its mmConfigs entry decides when set, else the asking agent's yolo. Overridden by AGENT_YOLO. bashTool, editTools, fetchContentTool still decide which tools exist. See How to confirm tool calls.
permissions string absent Deprecated, replaced by yolo. ask = yolo: false (and wins over yolo: true); auto changes nothing. Either value adds a start-up warning. Must be auto or ask.
mmConfigs map name → path, or name → {path, description, yolo, askUser} none Other mm agents this one can ask. Each name generates /ask-<name> and /ask-with-context-<name> (see slash commands), and the list declares the ask_agent tool, through which the model calls them itself. The long form adds description (when to call that agent; shown in ask_agent's description, falling back to the agent file's acp.title) and yolo (decides that agent's tool calls when it is asked, by /ask, ask_agent or mm --ask, instead of this agent's yolo: true they run, false each one is asked; absent, this agent's yolo decides) and askUser (whether that agent may ask the user questions when it is asked: true its ask_user questions come up to this agent's user, false its ask_user tool is not declared; absent, that agent's own askUserTool decides). path must not be empty. A relative path is relative to the directory of the file that declares it. Names: lowercase letters, digits and -, starting with a letter or a digit, not starting with with-context. A path whose file does not exist is a start-up warning, not an error.

context keys

Key Type Default Description
enabled bool false Turn the automatic compression on. /compact works regardless.
threshold int 75 Percentage of the context window beyond which the history is compressed before the next question. Between 1 and 100.
maxMessages int 80 Message count that triggers a compression when the window is unknown or the estimate is low. 0 disables. Must be ≥ 0.
keepLastTurns int 3 Question turns kept raw at the end of the history. Must be ≥ 1.
summaryMaxTokens int 1200 max_tokens of the summary request. Must be > 0. A thinking model's reasoning counts against it: on dmr, llamacpp and llmman the summary request turns reasoning off (chat_template_kwargs.enable_thinking: false), on ollama too (reasoning_effort: "none", the one switch Ollama obeys) — whatever sampling says; other chat_template_kwargs are kept, and a reasoning_effort set there is lowered to low (gpt-oss has no "off"); elsewhere, if the error says the model "reasoned until the limit", raise it.
prompt string "" Replaces the built-in summary prompt when not empty.
showStats bool true Print the one-line 🗜️ report after each compression.

sessions keys

Key Type Default Description
sessions.enabled bool false Save every conversation to a file, rewritten after each turn, /compact and /new. The file also records, outside the history, every error shown (errors array: failed generation, failed compaction, failed /resume). Enables /sessions and /resume in the terminal and session/load over ACP.
sessions.dir string .mm/sessions Directory of the session files, one <id>.json each. Relative to the directory mm is started from (terminal) or to the session's cwd (ACP); an absolute path is used as-is. Must not be empty.
sessions.searchMemory bool false The /memory <words> command (terminal, window, editor): searches the stored sessions of the project. Needs sessions.enabled. See slash commands and How to search earlier sessions.
sessions.autoSearchMemory bool false The search_memory tool: the same search, called by the model. Independent of searchMemory. Needs sessions.enabled.

Either memory key set while sessions.enabled is false prints the start-up warning sessions: searchMemory / autoSearchMemory need sessions.enabled: true — /memory and search_memory are off.

trace keys

Key Type Default Description
trace.enabled bool false Write one pair of files per model call: the request body as sent (after mm's own fields: stream_options, return_progress, a summary's reasoning switch), indented, and the response body as received. Overridden by AGENT_TRACE. Headers are never written. The banner ends with | trace: <absolute dir> while it is on ( | trace: <session cwd>/<dir> in ACP mode).
trace.dir string .mm/traces Directory of the files. A relative path is resolved per call in the working directory of the turn — the session's cwd in ACP mode, like sessions.dir — else in the directory mm runs in; an absolute path is used as-is. A directory that cannot be created prints [trace: cannot write to <dir>: <error> — nothing is traced] once (on stderr in ACP mode); the calls go on. Created when needed. Must not be empty when the trace is on.

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.

agentsMd keys

Key Type Default Description
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).
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.
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.

When the file is read, the system message is the system prompt, a blank line, then:

PROJECT INSTRUCTIONS (from <absolute path>)
The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins.

<the file, trimmed>

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.

acp keys

Key Type Default Description
acp.name string bob agentInfo.name in the initialize response. Must not be empty.
acp.title string Bob (bash-first agent) agentInfo.title, the name the editor may display. Empty: not sent. Also shown by the window mode (-tui) next to mm, in its header and above each answer — ‣ mm [Homer (coder)] — unless it is empty or the built-in default. During an /ask, the window shows the ASKED agent instead: its acp.title (or its mmConfigs name) above its answer, and in the header with its model and asked by <this agent>, until it has answered.

The version is not configurable: agentInfo.version is always the binary's, as printed by mm --version.

lsp keys

Key Type Default Description
lsp.servers list [] One entry per language server. The file extension picks the server.
lsp.servers[].name string — How the banner, the tool description and the warnings call it. Required.
lsp.servers[].command string — The executable, looked up on the PATH at start-up. Required. Not found: warning, server skipped.
lsp.servers[].args list of strings [] Its arguments (many servers need --stdio).
lsp.servers[].extensions list of strings — File extensions it serves, with the dot (.go, .ts). Required, at least one; matched case-insensitively; an extension may be served by one server only.
lsp:
  servers:
    - name: gopls
      command: gopls
      extensions: [".go"]
    - name: typescript
      command: typescript-language-server
      args: ["--stdio"]
      extensions: [".ts", ".tsx", ".js", ".jsx"]

The shipped agent.lsp.yaml is agent.dmr.yaml with this block on (gopls active, other servers ready to uncomment) and a system prompt that says when to use the tool.

tools keys

Key Type Default Description
tools list [] One entry per declared tool.
tools[].name string — What the model calls. Required. Starts with a letter; letters, digits, _, -; at most 64 characters. Unique. Not the name of a built-in tool (ask_agent, ask_user, bash, edit_file, fetch_content, lsp, plan, read_file, read_skill, save_answer, search_memory, unload_model, write_file), even one this agent turns off.
tools[].description string — What the tool does and when to use it, shown to the model. Required. A line Required parameters: … is appended.
tools[].parameters map name → parameter {} The arguments, in the order written. Names follow the name rules.
tools[].parameters.<p>.type string string string, number, integer or boolean.
tools[].parameters.<p>.description string "" What the value is, shown to the model.
tools[].parameters.<p>.enum list none The only values accepted, each of the parameter's type.
tools[].parameters.<p>.required bool true false makes the parameter optional.
tools[].parameters.<p>.default value none The value of an optional parameter the call leaves out. Only with required: false; of the parameter's type; inside the enum when there is one.
tools[].command list of strings — The program, then its arguments, one argv entry each. Required. {{p}} inside an entry is replaced by the value of parameter p. The program (first entry) cannot contain a placeholder. Every placeholder must name a declared parameter. A relative program (./scripts/x.sh) is resolved from the working directory.
tools[].timeout duration 0 (= bashTimeout) How long one call may run. Must be ≥ 0.
tools:
  - name: release
    description: Prepare a release of the project for a version and a channel.
    parameters:
      version: { type: string, description: "e.g. 1.4.2" }
      channel: { type: string, enum: [stable, beta, nightly] }
      dryRun:  { type: boolean, required: false, default: false }
    command: ["./scripts/release.sh", "{{version}}", "{{channel}}", "--dry-run={{dryRun}}"]
    timeout: 60s

Provider defaults

Provider baseUrl fallback apiKeyEnv Key required
dmr http://localhost:12434/engines/v1 http://host.docker.internal:12434/engines/v1 none no
llamacpp http://127.0.0.1:8080/v1 none LLAMA_API_KEY no
llmman http://127.0.0.1:17434/v1 http://host.docker.internal:17434/v1 LLMMAN_API_KEY no
ollama http://localhost:11434/v1 http://host.docker.internal:11434/v1 OLLAMA_API_KEY no

structuredOutput

A YAML block string holding Go declarations. Each type is a definition; the one var (var X T or var X = T{}) names the root of the answer. Only type and var declarations are allowed.

Go JSON Schema
string {"type": "string"}
bool {"type": "boolean"}
int, int8 … int64, uint … uint64 {"type": "integer"}
float32, float64 {"type": "number"}
[]T {"type": "array", "items": T}
map[string]T {"type": "object", "additionalProperties": T}
struct { … } {"type": "object", "properties": …, "required": [every key], "additionalProperties": false}, keys in declaration order
a declared type Name {"$ref": "#/$defs/Name"}; a struct root is inlined, $defs holds the types the root reaches
*T field, or json:",omitempty" the key stays required, its value is anyOf: [T, null]
Field element Effect
json:"name" tag the key; without a tag, the Go field name as written
json:"-" tag field skipped
// comment above or beside a declaration or field its description

Refused, with structuredOutput:<line>:<col>: … counted from the first line of the block: func, import, const; generic types; aliases (type A = B); embedded fields; fixed-size arrays; map keys other than string; interface, chan, func, qualified (time.Time) and undefined types; a pointer outside a struct field; a type declared twice; a key used twice; zero or more than one var; a var without a type.

When the block is not empty:

  • the declarations are appended to system, under an OUTPUT FORMAT paragraph;
  • every model call carries response_format: {"type": "json_schema", "json_schema": {"name": "output", "strict": true, "schema": …}};
  • the answer is validated against the schema; one that does not match fails the turn with model failed to generate output matching expected schema: …, prefixed with the answer was cut off by max_tokens before its JSON was complete … when the server stopped on max_tokens;
  • -p prints the JSON answer alone on stdout (see Prompt mode specifics);
  • the banner shows tools: none (structured output: JSON answers) and no "no skills" / "no tool" warning.

Example:

structuredOutput: |
  type Language struct {
      Name     string `json:"name"`     // the language's name, in English
      Official bool   `json:"official"`
  }
  type Country struct {
      Name      string     `json:"name"`
      Capital   *string    `json:"capital"`   // null when unknown
      Languages []Language `json:"languages"` // at most 4
  }
  var Countries = []Country{}

Validation errors

Reported on stderr as [config error: <path>: <message>], exit code 1 — in every mode, so that -p … | jq never reads an error as the answer. [engine error: …] and [usage: …] too.

Message Cause
provider must not be empty provider: ""
model must not be empty model: ""
contextWindow must be >= 0 negative value
maxOutput must be > 0 zero or negative
maxTurns must be > 0 zero or negative
previewLines must be >= 0 negative value
context.threshold must be between 1 and 100 out of range
context.keepLastTurns must be >= 1 zero or negative
context.summaryMaxTokens must be > 0 zero or negative
context.maxMessages must be >= 0 negative value
sessions.dir must not be empty empty string
trace.dir must not be empty empty string with the trace on
agentsMd.path must not be empty empty string with agentsMd.enabled on
agentsMd.maxChars must be >= 0 negative value
acp.name must not be empty empty or blank string
lsp.servers[N].name must not be empty a server without name
lsp.servers[N].command must not be empty a server without command
lsp.servers[N].extensions must list at least one extension a server without extensions
lsp.servers[N].extensions: "go" must start with a dot, like ".go" an extension without its dot
lsp.servers[N]: extension ".ts" is already served by "typescript" two servers on one extension
tools[N] (<name>).name "<name>" must start with a letter and hold only letters, digits, _ or - (64 characters at most) an invalid tool name
tools[N] (<name>).name "<name>" is the name of a built-in tool a declared tool named like a built-in tool
tools[N] (<name>): another tool is already called "<name>" two entries with one name
tools[N] (<name>).description must not be empty: … no description
tools[N] (<name>).command must name a program, … no command, or an empty first entry
tools[N] (<name>).command: the program ("{{p}}") cannot come from a parameter a placeholder in the first entry
tools[N] (<name>).command: {{p}} is not a declared parameter a placeholder no parameter declares
tools[N] (<name>).timeout must be >= 0 a negative timeout
tools[N] (<name>).parameters.<p>.type "x" must be one of string, number, integer, boolean an unknown type
tools[N] (<name>).parameters.<p>.enum: … an enum value not of the parameter's type
tools[N] (<name>).parameters.<p>.default only makes sense with required: false a default on a required parameter
tools[N] (<name>).parameters.<p>.default: …, ….default <v> is not one of the enum values a default of the wrong type, or outside the enum
tools[N] (<name>).parameters.<p>: declared twice, …: a parameter name must start with a letter … a repeated or invalid parameter name
line N: parameters must be a mapping of name → {type, description, …} parameters: written as a list
permissions must be "auto" or "ask" any other value of the deprecated key
AGENT_YOLO="maybe": must be true or false an AGENT_YOLO value strconv.ParseBool refuses (accepted: 1, t, true, 0, f, false, any case)
bashTimeout must be > 0 zero or negative
structuredOutput:<line>:<col>: … a declaration the structuredOutput section cannot translate
structuredOutput: no var declaration: … no var names the root
structuredOutput cannot be used with tools — this agent declares: <tools> (…) a structuredOutput with at least one declared tool (bash, plan, the file tools, fetch_content, read_skill, ask_agent, lsp, MCP tools, declared tools). Reported after the tools are assembled, before any front end starts.
mmConfigs: "Bob" is not a valid name (…) a name with uppercase letters, spaces or other characters, or starting with -
mmConfigs: "with-context-x" is not a valid name (it would read as /ask-with-context-…) a name starting with with-context
mmConfigs.<name>: the path must not be empty <name>: "", or a long form without path
hooks: unknown event "x" (…), hooks.<event>[N]: … an invalid hooks: block — see hooks
unknown provider "x" (known: dmr, llamacpp, llmman, ollama) Reported by the engine, as [engine error: …].

A start-up warning, not an error, is printed when context.enabled is true, the window is unknown and context.maxMessages is 0, and when a declared tool has the name of an MCP tool (tools: <name> skipped — an MCP tool already has that name; the tool is not declared), and when the file sets the deprecated permissions key (permissions: ask is deprecated — use yolo: false instead …).

Shipped files

File Purpose
agent.yaml Docker Model Runner set-up: provider: dmr, maxTurns: 40, displayCommands: true, compression off, a system prompt named "Bob" that describes the skills and file-editing rules.
agent.llamacpp.yaml llama.cpp set-up: provider: llamacpp, baseUrl: http://127.0.0.1:8080/v1, fallback: "", compression on.

Example

provider: llamacpp
model: my-alias
baseUrl: http://127.0.0.1:8080/v1
fallback: ""
maxTurns: 40
context:
  enabled: true
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# Reference: configuration file

> Neutral, exhaustive description of the YAML file read by `mm`. Every key is optional; a key left out keeps its built-in default. The file is decoded on top of the defaults, so unknown keys are ignored.

## Top-level keys

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `provider` | string | `dmr` | Registry key of the LLM server: `dmr`, `llamacpp`, `llmman` or `ollama`. Overridden by `AGENT_PROVIDER`. |
| `model` | string | `huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M` | Model identifier in the provider's naming. Overridden by `AGENT_MODEL`. |
| `baseUrl` | string | `""` (provider default) | OpenAI-compatible endpoint tried first. Overridden by `AGENT_BASE_URL`. |
| `fallback` | string or absent | absent (provider default) | Endpoint used when `baseUrl` does not answer `/models` within 2 s. Absent = provider default; `""` = no fallback. |
| `apiKeyEnv` | string | `""` (provider default) | Name of the environment variable holding the API key. |
| `contextWindow` | int | `0` | Tokens the server serves. `0` = ask the server, else unknown. Must be ≥ 0. |
| `maxOutput` | int | `16000` | Maximum characters a tool returns to the model; longer output keeps its head and up to 2000 characters of tail. Must be > 0. |
| `maxTurns` | int | `10` | Maximum model ↔ tools round trips per question. Must be > 0. |
| `previewLines` | int | `20` | Lines of a command's output echoed to the terminal. `0` disables the echo. Must be ≥ 0. |
| `displayCommands` | bool | `false` | Print the numbered list of commands and file operations after each answer. |
| `displayHooks` | bool | `false` | Show every hook that ran: event, command, tool or agent, decision, size of the context added (never its text). After each answer in the line mode, `-p` and the window; as a finished tool call per hook in an editor. See [hooks](hooks.md#display). |
| `showThinking` | bool | `true` | Show what a thinking model reasons about before it answers. Only the display is optional: the reasoning is always read off the wire, because it is what keeps the watchdog alive while the model thinks. See [Thinking models](../explanation/thinking-models.md). |
| `system` | string | built-in prompt | The system prompt, first message of every history. |
| `structuredOutput` | string (Go declarations) | `""` | The shape every answer must follow, as JSON: Go `type` declarations and one `var` naming the root of the answer. See [`structuredOutput`](#structuredoutput) below. Empty = free-text answers. Incompatible with any declared tool. |
| `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. |
| `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. |
| `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |
| `fetchContentTool` | bool | `false` | Declare `fetch_content`, which fetches web pages as Markdown. Off by default: it is the only built-in tool that reaches the network. |
| `askUserTool` | bool | `true` | Declare `ask_user`, through which the model asks the user questions (pick one, pick several, type a text, yes or no) and waits for the answers. `false` removes it: the model cannot ask. Never declared beside a `structuredOutput`. Overridden by `AGENT_ASK_USER`; for an agent asked by another one, also by the asker's `mmConfigs.<name>.askUser`. See [tools](tools.md#ask_user). |
| `saveAnswerTool` | bool | `false` | Declare `save_answer`, which writes an answer the model already gave to a new file: the model sends a file name, not the text again. Off by default. The `/save` command exists either way. See [tools](tools.md#save_answer). |
| `unloadModels` | bool | `true` | The `/unload [model]` command (terminal, window, editor) and the `unload_model` tool, which free a model from the server's memory: llmman and Ollama, llama-server in router mode. `false` removes both. The tool is never declared beside a `structuredOutput`. See [tools](tools.md#unload_model) and [How to free a model's memory](../how-to/unload-a-model.md). |
| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. A key the OpenAI API does not declare is sent as it is, at the top level of the request — for example `chat_template_kwargs: {enable_thinking: false}`, which tells a llama.cpp server (`dmr`, `llamacpp`, `llmman`) not to reason; `reasoning_effort: none` does the same on Ollama. `messages`, `tools`, `tool_choice` (and the legacy `functions`, `function_call`) are ignored: Genkit builds them from the conversation. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). |
| `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
| `firstTokenTimeout` | duration | `3m` | Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than `watchdogTimeout`; must be >= 0. With a llama.cpp server (`dmr`, `llamacpp`, `llmman` — whose local GGUF models run in llama-server) mm asks for `return_progress`: each progress report restarts this clock, and the spinner shows `Reading the prompt 12.0k/25.3k tokens`. Ollama sends no progress: this one wait covers the whole prompt (and the model's loading). On a timeout the watchdog says `no first token from the server after …` (before the first token) or `connection hang detected after …` (after). |
| `bashTimeout` | duration | `30s` | How long one `bash` command may run before it is killed; the tool then answers `[error: <bashTimeout> timeout exceeded]`. Must be > 0. The watchdog keeps counting while a command runs (ticket 57): a command longer than `watchdogTimeout` ends the turn, so raise both together. |
| `context` | map | see below | Automatic history compression. |
| `sessions` | map | see below | Persistent, per-project sessions. |
| `trace` | map | see below | Write every request to the model and every response to files. |
| `agentsMd` | map | see below | Append the project's `AGENTS.md` to the system prompt of every conversation. On by default. |
| `acp` | map | see below | How `mm -acp` introduces itself to the editor. |
| `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. |
| `hooks` | map event → list | none | Commands run before a question reaches the model (`preGeneration`), before and after each tool call (`preToolCall`, `postToolCall`) after the turn (`postGeneration`), and before and after each hand-off to another agent (`preAsk`, `postAsk`); each reads one JSON object on stdin and may answer one on stdout. Entry keys: `command` (required), `matcher` (tool and agent events only: a tool name, or an agent name), `timeout` (default `10s`), `onError` (`fail` \| `warn` \| `ignore`, default `warn`). See [hooks](hooks.md). |
| `tools` | list | `[]` | Commands declared as typed tools: each entry a `name`, a `description`, its `parameters` and a `command` run without a shell. See [`tools` keys](#tools-keys) below and [declared tools](tools.md#declared-tools). |
| `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`. |
| `yolo` | bool | `true` | `true`: the tools run without asking. `false`: every call that changes something — `bash`, `write_file`, `edit_file`, `fetch_content` with `output`, `lsp` `rename`, `plan` `create`/`update`/`next`/`delete` — waits for the user's answer in every front end (line mode, window, editor), and so does every MCP tool, whatever `mcp.permissions` says. Answers: allow once, allow this tool for the session, reject this call, never (this tool, for the session). `read_file`, `read_skill`, `fetch_content` without `output` and the `lsp` navigation actions never ask. In `-p` and `mm --ask`, nobody can answer: those calls are rejected. An agent started by `/ask`, `ask_agent` or `mm --ask` runs with `AGENT_YOLO=false` and submits its calls to the asking agent: the `yolo` of its `mmConfigs` entry decides when set, else the asking agent's `yolo`. Overridden by `AGENT_YOLO`. `bashTool`, `editTools`, `fetchContentTool` still decide which tools exist. See [How to confirm tool calls](../how-to/confirm-tool-calls.md). |
| `permissions` | string | absent | Deprecated, replaced by `yolo`. `ask` = `yolo: false` (and wins over `yolo: true`); `auto` changes nothing. Either value adds a start-up warning. Must be `auto` or `ask`. |
| `mmConfigs` | map name → path, or name → `{path, description, yolo, askUser}` | none | Other mm agents this one can ask. Each name generates `/ask-<name>` and `/ask-with-context-<name>` (see [slash commands](slash-commands.md#asking-another-agent)), and the list declares the [`ask_agent`](tools.md#ask_agent) tool, through which the model calls them itself. The long form adds `description` (when to call that agent; shown in `ask_agent`'s description, falling back to the agent file's `acp.title`) and `yolo` (decides that agent's tool calls when it is asked, by `/ask`, `ask_agent` or `mm --ask`, instead of this agent's `yolo`: `true` they run, `false` each one is asked; absent, this agent's `yolo` decides) and `askUser` (whether that agent may ask the user questions when it is asked: `true` its `ask_user` questions come up to this agent's user, `false` its `ask_user` tool is not declared; absent, that agent's own `askUserTool` decides). `path` must not be empty. A relative path is relative to the directory of the file that declares it. Names: lowercase letters, digits and `-`, starting with a letter or a digit, not starting with `with-context`. A path whose file does not exist is a start-up warning, not an error. |

## `context` keys

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `enabled` | bool | `false` | Turn the automatic compression on. `/compact` works regardless. |
| `threshold` | int | `75` | Percentage of the context window beyond which the history is compressed before the next question. Between 1 and 100. |
| `maxMessages` | int | `80` | Message count that triggers a compression when the window is unknown or the estimate is low. `0` disables. Must be ≥ 0. |
| `keepLastTurns` | int | `3` | Question turns kept raw at the end of the history. Must be ≥ 1. |
| `summaryMaxTokens` | int | `1200` | `max_tokens` of the summary request. Must be > 0. A thinking model's reasoning counts against it: on `dmr`, `llamacpp` and `llmman` the summary request turns reasoning off (`chat_template_kwargs.enable_thinking: false`), on `ollama` too (`reasoning_effort: "none"`, the one switch Ollama obeys) — whatever `sampling` says; other `chat_template_kwargs` are kept, and a `reasoning_effort` set there is lowered to `low` (gpt-oss has no "off"); elsewhere, if the error says the model "reasoned until the limit", raise it. |
| `prompt` | string | `""` | Replaces the built-in summary prompt when not empty. |
| `showStats` | bool | `true` | Print the one-line `🗜️` report after each compression. |

## `sessions` keys

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `sessions.enabled` | bool | `false` | Save every conversation to a file, rewritten after each turn, `/compact` and `/new`. The file also records, outside the history, every error shown (`errors` array: failed generation, failed compaction, failed `/resume`). Enables `/sessions` and `/resume` in the terminal and `session/load` over ACP. |
| `sessions.dir` | string | `.mm/sessions` | Directory of the session files, one `<id>.json` each. Relative to the directory `mm` is started from (terminal) or to the session's `cwd` (ACP); an absolute path is used as-is. Must not be empty. |
| `sessions.searchMemory` | bool | `false` | The `/memory <words>` command (terminal, window, editor): searches the stored sessions of the project. Needs `sessions.enabled`. See [slash commands](slash-commands.md) and [How to search earlier sessions](../how-to/search-earlier-sessions.md). |
| `sessions.autoSearchMemory` | bool | `false` | The [`search_memory`](tools.md#search_memory) tool: the same search, called by the model. Independent of `searchMemory`. Needs `sessions.enabled`. |

Either memory key set while `sessions.enabled` is false prints the start-up warning `sessions: searchMemory / autoSearchMemory need sessions.enabled: true — /memory and search_memory are off`.

## `trace` keys

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `trace.enabled` | bool | `false` | Write one pair of files per model call: the request body as sent (after mm's own fields: `stream_options`, `return_progress`, a summary's reasoning switch), indented, and the response body as received. Overridden by `AGENT_TRACE`. Headers are never written. The banner ends with ` \| trace: <absolute dir>` while it is on (` \| trace: <session cwd>/<dir>` in ACP mode). |
| `trace.dir` | string | `.mm/traces` | Directory of the files. A relative path is resolved per call in the working directory of the turn — the session's `cwd` in ACP mode, like `sessions.dir` — else in the directory `mm` runs in; an absolute path is used as-is. A directory that cannot be created prints `[trace: cannot write to <dir>: <error> — nothing is traced]` once (on stderr in ACP mode); the calls go on. Created when needed. Must not be empty when the trace is on. |

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`.

## `agentsMd` keys

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `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). |
| `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`. |
| `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. |

When the file is read, the system message is the `system` prompt, a blank line, then:

```text
PROJECT INSTRUCTIONS (from <absolute path>)
The instructions of the project you work in. Follow them; when the user asks otherwise in the conversation, the user wins.

<the file, trimmed>
```

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.

## `acp` keys

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `acp.name` | string | `bob` | `agentInfo.name` in the `initialize` response. Must not be empty. |
| `acp.title` | string | `Bob (bash-first agent)` | `agentInfo.title`, the name the editor may display. Empty: not sent. Also shown by the window mode (`-tui`) next to `mm`, in its header and above each answer — `‣ mm [Homer (coder)]` — unless it is empty or the built-in default. During an `/ask`, the window shows the ASKED agent instead: its `acp.title` (or its `mmConfigs` name) above its answer, and in the header with its model and `asked by <this agent>`, until it has answered. |

The version is not configurable: `agentInfo.version` is always the binary's, as printed by `mm --version`.

## `lsp` keys

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `lsp.servers` | list | `[]` | One entry per language server. The file extension picks the server. |
| `lsp.servers[].name` | string | — | How the banner, the tool description and the warnings call it. Required. |
| `lsp.servers[].command` | string | — | The executable, looked up on the `PATH` at start-up. Required. Not found: warning, server skipped. |
| `lsp.servers[].args` | list of strings | `[]` | Its arguments (many servers need `--stdio`). |
| `lsp.servers[].extensions` | list of strings | — | File extensions it serves, with the dot (`.go`, `.ts`). Required, at least one; matched case-insensitively; an extension may be served by one server only. |

```yaml
lsp:
  servers:
    - name: gopls
      command: gopls
      extensions: [".go"]
    - name: typescript
      command: typescript-language-server
      args: ["--stdio"]
      extensions: [".ts", ".tsx", ".js", ".jsx"]
```

The shipped `agent.lsp.yaml` is `agent.dmr.yaml` with this block on (gopls active, other servers ready to uncomment) and a system prompt that says when to use the tool.

## `tools` keys

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `tools` | list | `[]` | One entry per declared tool. |
| `tools[].name` | string | — | What the model calls. Required. Starts with a letter; letters, digits, `_`, `-`; at most 64 characters. Unique. Not the name of a built-in tool (`ask_agent`, `ask_user`, `bash`, `edit_file`, `fetch_content`, `lsp`, `plan`, `read_file`, `read_skill`, `save_answer`, `search_memory`, `unload_model`, `write_file`), even one this agent turns off. |
| `tools[].description` | string | — | What the tool does and when to use it, shown to the model. Required. A line `Required parameters: …` is appended. |
| `tools[].parameters` | map name → parameter | `{}` | The arguments, in the order written. Names follow the `name` rules. |
| `tools[].parameters.<p>.type` | string | `string` | `string`, `number`, `integer` or `boolean`. |
| `tools[].parameters.<p>.description` | string | `""` | What the value is, shown to the model. |
| `tools[].parameters.<p>.enum` | list | none | The only values accepted, each of the parameter's type. |
| `tools[].parameters.<p>.required` | bool | `true` | `false` makes the parameter optional. |
| `tools[].parameters.<p>.default` | value | none | The value of an optional parameter the call leaves out. Only with `required: false`; of the parameter's type; inside the `enum` when there is one. |
| `tools[].command` | list of strings | — | The program, then its arguments, one argv entry each. Required. `{{p}}` inside an entry is replaced by the value of parameter `p`. The program (first entry) cannot contain a placeholder. Every placeholder must name a declared parameter. A relative program (`./scripts/x.sh`) is resolved from the working directory. |
| `tools[].timeout` | duration | `0` (= `bashTimeout`) | How long one call may run. Must be ≥ 0. |

```yaml
tools:
  - name: release
    description: Prepare a release of the project for a version and a channel.
    parameters:
      version: { type: string, description: "e.g. 1.4.2" }
      channel: { type: string, enum: [stable, beta, nightly] }
      dryRun:  { type: boolean, required: false, default: false }
    command: ["./scripts/release.sh", "{{version}}", "{{channel}}", "--dry-run={{dryRun}}"]
    timeout: 60s
```

## Provider defaults

| Provider | `baseUrl` | `fallback` | `apiKeyEnv` | Key required |
|----------|-----------|------------|-------------|--------------|
| `dmr` | `http://localhost:12434/engines/v1` | `http://host.docker.internal:12434/engines/v1` | none | no |
| `llamacpp` | `http://127.0.0.1:8080/v1` | none | `LLAMA_API_KEY` | no |
| `llmman` | `http://127.0.0.1:17434/v1` | `http://host.docker.internal:17434/v1` | `LLMMAN_API_KEY` | no |
| `ollama` | `http://localhost:11434/v1` | `http://host.docker.internal:11434/v1` | `OLLAMA_API_KEY` | no |

## `structuredOutput`

A YAML block string holding Go declarations. Each `type` is a definition; the one `var` (`var X T` or `var X = T{}`) names the root of the answer. Only `type` and `var` declarations are allowed.

| Go | JSON Schema |
|----|-------------|
| `string` | `{"type": "string"}` |
| `bool` | `{"type": "boolean"}` |
| `int`, `int8` … `int64`, `uint` … `uint64` | `{"type": "integer"}` |
| `float32`, `float64` | `{"type": "number"}` |
| `[]T` | `{"type": "array", "items": T}` |
| `map[string]T` | `{"type": "object", "additionalProperties": T}` |
| `struct { … }` | `{"type": "object", "properties": …, "required": [every key], "additionalProperties": false}`, keys in declaration order |
| a declared type `Name` | `{"$ref": "#/$defs/Name"}`; a struct root is inlined, `$defs` holds the types the root reaches |
| `*T` field, or `json:",omitempty"` | the key stays required, its value is `anyOf: [T, null]` |

| Field element | Effect |
|---------------|--------|
| `json:"name"` tag | the key; without a tag, the Go field name as written |
| `json:"-"` tag | field skipped |
| `//` comment above or beside a declaration or field | its `description` |

Refused, with `structuredOutput:<line>:<col>: …` counted from the first line of the block: `func`, `import`, `const`; generic types; aliases (`type A = B`); embedded fields; fixed-size arrays; map keys other than `string`; `interface`, `chan`, `func`, qualified (`time.Time`) and undefined types; a pointer outside a struct field; a type declared twice; a key used twice; zero or more than one `var`; a `var` without a type.

When the block is not empty:

- the declarations are appended to `system`, under an `OUTPUT FORMAT` paragraph;
- every model call carries `response_format: {"type": "json_schema", "json_schema": {"name": "output", "strict": true, "schema": …}}`;
- the answer is validated against the schema; one that does not match fails the turn with `model failed to generate output matching expected schema: …`, prefixed with `the answer was cut off by max_tokens before its JSON was complete …` when the server stopped on `max_tokens`;
- `-p` prints the JSON answer alone on stdout (see [Prompt mode specifics](cli.md#prompt-mode-specifics));
- the banner shows `tools: none (structured output: JSON answers)` and no "no skills" / "no tool" warning.

Example:

```yaml
structuredOutput: |
  type Language struct {
      Name     string `json:"name"`     // the language's name, in English
      Official bool   `json:"official"`
  }
  type Country struct {
      Name      string     `json:"name"`
      Capital   *string    `json:"capital"`   // null when unknown
      Languages []Language `json:"languages"` // at most 4
  }
  var Countries = []Country{}
```

## Validation errors

Reported on stderr as `[config error: <path>: <message>]`, exit code 1 — in every mode, so that `-p … | jq` never reads an error as the answer. `[engine error: …]` and `[usage: …]` too.

| Message | Cause |
|---------|-------|
| `provider must not be empty` | `provider: ""` |
| `model must not be empty` | `model: ""` |
| `contextWindow must be >= 0` | negative value |
| `maxOutput must be > 0` | zero or negative |
| `maxTurns must be > 0` | zero or negative |
| `previewLines must be >= 0` | negative value |
| `context.threshold must be between 1 and 100` | out of range |
| `context.keepLastTurns must be >= 1` | zero or negative |
| `context.summaryMaxTokens must be > 0` | zero or negative |
| `context.maxMessages must be >= 0` | negative value |
| `sessions.dir must not be empty` | empty string |
| `trace.dir must not be empty` | empty string with the trace on |
| `agentsMd.path must not be empty` | empty string with `agentsMd.enabled` on |
| `agentsMd.maxChars must be >= 0` | negative value |
| `acp.name must not be empty` | empty or blank string |
| `lsp.servers[N].name must not be empty` | a server without `name` |
| `lsp.servers[N].command must not be empty` | a server without `command` |
| `lsp.servers[N].extensions must list at least one extension` | a server without `extensions` |
| `lsp.servers[N].extensions: "go" must start with a dot, like ".go"` | an extension without its dot |
| `lsp.servers[N]: extension ".ts" is already served by "typescript"` | two servers on one extension |
| `tools[N] (<name>).name "<name>" must start with a letter and hold only letters, digits, _ or - (64 characters at most)` | an invalid tool name |
| `tools[N] (<name>).name "<name>" is the name of a built-in tool` | a declared tool named like a built-in tool |
| `tools[N] (<name>): another tool is already called "<name>"` | two entries with one name |
| `tools[N] (<name>).description must not be empty: …` | no `description` |
| `tools[N] (<name>).command must name a program, …` | no `command`, or an empty first entry |
| `tools[N] (<name>).command: the program ("{{p}}") cannot come from a parameter` | a placeholder in the first entry |
| `tools[N] (<name>).command: {{p}} is not a declared parameter` | a placeholder no parameter declares |
| `tools[N] (<name>).timeout must be >= 0` | a negative `timeout` |
| `tools[N] (<name>).parameters.<p>.type "x" must be one of string, number, integer, boolean` | an unknown type |
| `tools[N] (<name>).parameters.<p>.enum: …` | an `enum` value not of the parameter's type |
| `tools[N] (<name>).parameters.<p>.default only makes sense with required: false` | a `default` on a required parameter |
| `tools[N] (<name>).parameters.<p>.default: …`, `….default <v> is not one of the enum values` | a `default` of the wrong type, or outside the `enum` |
| `tools[N] (<name>).parameters.<p>: declared twice`, `…: a parameter name must start with a letter …` | a repeated or invalid parameter name |
| `line N: parameters must be a mapping of name → {type, description, …}` | `parameters:` written as a list |
| `permissions must be "auto" or "ask"` | any other value of the deprecated key |
| `AGENT_YOLO="maybe": must be true or false` | an `AGENT_YOLO` value `strconv.ParseBool` refuses (accepted: `1`, `t`, `true`, `0`, `f`, `false`, any case) |
| `bashTimeout must be > 0` | zero or negative |
| `structuredOutput:<line>:<col>: …` | a declaration the [`structuredOutput`](#structuredoutput) section cannot translate |
| `structuredOutput: no var declaration: …` | no `var` names the root |
| `structuredOutput cannot be used with tools — this agent declares: <tools> (…)` | a `structuredOutput` with at least one declared tool (`bash`, `plan`, the file tools, `fetch_content`, `read_skill`, `ask_agent`, `lsp`, MCP tools, declared `tools`). Reported after the tools are assembled, before any front end starts. |
| `mmConfigs: "Bob" is not a valid name (…)` | a name with uppercase letters, spaces or other characters, or starting with `-` |
| `mmConfigs: "with-context-x" is not a valid name (it would read as /ask-with-context-…)` | a name starting with `with-context` |
| `mmConfigs.<name>: the path must not be empty` | `<name>: ""`, or a long form without `path` |
| `hooks: unknown event "x" (…)`, `hooks.<event>[N]: …` | an invalid `hooks:` block — see [hooks](hooks.md#validation-errors) |
| `unknown provider "x" (known: dmr, llamacpp, llmman, ollama)` | Reported by the engine, as `[engine error: …]`. |

A start-up warning, not an error, is printed when `context.enabled` is `true`, the window is unknown and `context.maxMessages` is `0`, and when a declared tool has the name of an MCP tool (`tools: <name> skipped — an MCP tool already has that name`; the tool is not declared), and when the file sets the deprecated `permissions` key (`permissions: ask is deprecated — use yolo: false instead …`).

## Shipped files

| File | Purpose |
|------|---------|
| `agent.yaml` | Docker Model Runner set-up: `provider: dmr`, `maxTurns: 40`, `displayCommands: true`, compression off, a system prompt named "Bob" that describes the skills and file-editing rules. |
| `agent.llamacpp.yaml` | llama.cpp set-up: `provider: llamacpp`, `baseUrl: http://127.0.0.1:8080/v1`, `fallback: ""`, compression on. |

### Example

```yaml
provider: llamacpp
model: my-alias
baseUrl: http://127.0.0.1:8080/v1
fallback: ""
maxTurns: 40
context:
  enabled: true
```