bots-garden/mini-mepublic Fork
⑂ v0.1.4
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. 37a5a39 · on v0.1.4 · k33g · Sep 21, 2026
acp.md · 133 lines · 10.1 KBmarkdown
Blame HistoryOpen raw

Reference: Agent Client Protocol surface

Neutral description of what mm -acp exposes over JSON-RPC 2.0 on stdio, through github.com/coder/acp-go-sdk v0.13.5.

Transport

Aspect Value
Input stdin, one JSON-RPC message per line.
Output stdout, JSON-RPC messages only.
Logs stderr: banner, warnings, [acp] … lines, SDK logger.
Lifetime Until the client closes stdin or the process context is cancelled.

initialize response

Field Value
protocolVersion 1
agentCapabilities loadSession: true when sessions.enabled is set, absent (false) otherwise. Everything else absent (false): no image or audio prompts, no HTTP MCP.
agentInfo.name bob
agentInfo.title Bob (bash-first agent)
agentInfo.version 0.11.0
authMethods []

Methods

Method Behaviour
initialize Logs the client name and protocol version on stderr; returns the values above.
authenticate Returns an empty response.
session/new Creates a session with a time-sortable id (20260916-101530-4f2a9c1e) and the request's cwd, a fresh history holding the system prompt, and an empty allow-always set. mcpServers are counted in the log and ignored. Once the response is written, sends an available_commands_update notification listing the slash commands (see below). With sessions.enabled, writes <cwd>/<sessions.dir>/<id>.json at once and rewrites it after every turn, /new and /compact.
session/prompt Runs one generation on the session's history, or executes a slash command without consulting the model. Turns are serialised process-wide.
session/cancel Cancels the turn in flight for that session.
session/load With sessions.enabled: reads <cwd>/<sessions.dir>/<sessionId>.json, replaces its system prompt by the current one, registers the session (empty allow-always set), sends the replay (below) then an available_commands_update, and answers {}. Unknown id or unreadable file: invalid params carrying the sessionId and the error. Without sessions.enabled: method not found.
session/set_mode, session/set_config_option, session/list, session/resume, session/close, logout method not found.

Skills as commands

ACP has no notion of a skill. A prompt carries text, image, audio, resource or resource_link — the last being what an editor's @ file picker sends — and there is nothing for "run this procedure".

AvailableCommand is the slot, and the protocol's own examples say so: its documentation offers create_plan and research_codebase, which are procedures rather than session commands. So each skill of the catalogue is advertised there, and the editor lists it in its own / menu:

/compact                                              Compress the history now, keeping the last turns
/new                                                  Clear the history and start a new session
/bg-jobs          [input: parameters, if …]           Start, wait on, inspect and stop a long-running background job…
/greetings        [input: parameters, if …]           Greet somebody with a festive message.
/vulcan-salute    [input: parameters, if …]           Send a vulcan salute to somebody.

The input.hint is what carries the parameters: the protocol says "all text that was typed after the command name is provided as input", which is exactly what a skill takes.

Aspect Behaviour
What happens on /greetings Bob Not intercepted. It is rewritten as the $greetings Bob notation and the turn runs normally, so the model still makes the read_skill call — the loop detector, the permission gate and the 📖 N skill(s) recap all still see it, and mm never loads a skill behind the model's back.
A real command wins session/prompt tries the interception first, so a skill called compact or new can never shadow one. Such a skill is not advertised either, and the start-up banner says so — otherwise it would be silently unreachable from the menu.
An unknown /name Left exactly as typed and handed to the model, as any arbitrary slash text was before.
Typing $greetings Bob by hand Works too, in the editor as in the terminal: the notation is resolved in session/prompt whether the menu wrote it or the user did.

Note the invariant changed with this: it used to be "only advertise what session/prompt intercepts", and it is now "only advertise what this front end handles" — rewriting is handling.

session/load replay

Sent before the response, one notification per element of the stored history, in order. The system prompt is not sent.

Stored element Notification
User message user_message_chunk with its text.
Compression summary (user-role message marked by /compact) agent_message_chunk with 🗜️ followed by the summary.
Model text agent_message_chunk.
Tool call with its recorded response tool_call with toolCallId = the call's ref, title and kind as the live tool would give (bash: <command> execute, read_file: <path> read, write_file/edit_file edit, read_skill: <name> read), status: completed, rawInput, rawOutput.output and a text content holding the output.
Tool call without a recorded response tool_call with status: failed and no content.

No tool runs during a replay. Logged as [acp] session <id> loaded from <dir>: N message(s) replayed.

session/prompt

Aspect Behaviour
Unknown sessionId invalid params error carrying the sessionId.
Prompt content text blocks concatenated; a resource_link becomes \n[attached file: <path without file://>]; other block types are ignored. Then every @path in the text that names an existing file or directory under the session's cwd (or an absolute / ~ path) appends \n[attached file: <absolute path>] or \n[attached directory: …], once per path, and is logged as [acp] session <id>: attached <path>.
Slash command When the flattened text, trimmed, is exactly /new or /compact, no generation runs; the command answers with one agent_message_chunk and stopReason: end_turn.
/new The session's history is reset to the system prompt alone, the engine's last input-token count is forgotten, the chunk is 🆕 New session: N message(s) forgotten.. The session id, cwd and allow-always grants are kept. With sessions.enabled, the conversation so far is saved under a new id first (logged previous conversation kept as <id>; nothing saved when it was empty) and the session's own file is rewritten with the fresh history. Logged as [acp] session <id>: new session, N message(s) forgotten.
/compact The history is compressed with the context settings, forced (the threshold is ignored, keepLastTurns is honoured). The chunk is 🗜️ compressed N messages → 1 summary + N kept (… tokens, …s), or 🗜️ nothing to compact: N message(s), no turn older than the last K (history unchanged), or [compact: failed, history kept: <explanation>] (history unchanged). The token count is forgotten only when the history changed. Waits for a running turn: turns are serialised. Logged as [acp] session <id>: /compact — <line>.
History The full history returned by the engine is kept, even after a failure or a cancellation; a question with no answer at all is removed.
Success stopReason: end_turn.
Cancelled stopReason: cancelled.
Failure internal error with error: <one-line explanation>.
Log line [acp] turn done: N command(s) · N file op(s) · N skill(s) on stderr.

session/update notifications sent

Update When
available_commands_update Once per session, right after the session/new response has been written (the editor must know the session id first, or it drops the update). Lists what this front end handles: the slash commands session/prompt intercepts, then one command per skill. Also sent at the end of a session/load replay.
agent_message_chunk (text) Each streamed chunk of the model's answer, whitespace preserved; also the one-line confirmation of a slash command.
tool_call status pending Before a tool runs; carries title, kind, rawInput, and locations with the absolute path when a file is involved.
tool_call_update status in_progress Permission granted, tool executing.
tool_call_update status completed or failed Tool finished; content is a diff for file changes, else a text block with the output; rawOutput.output always carries the output.

Available commands

name description input
new Clear the history and start a new session none
compact Compress the history now, keeping the last turns none
one per skill the skill's description { "hint": "parameters, if this skill needs any" }

Clients display the name with a leading slash and send it back as the text of a session/prompt. The two commands above are intercepted there; a skill's command is rewritten into the $skill notation and the turn runs — see Skills as commands.

session/request_permission requests sent

Sent before bash, read_file, write_file and edit_file run; never for read_skill.

Option id Kind Effect
allow allow_once Runs the tool.
allow_always allow_always Runs the tool and skips the dialog for that tool name for the rest of the session.
reject reject_once Does not run; the model receives [command rejected by the user].

A transport error or a cancelled outcome counts as a rejection.

Tool kinds

mm kind ACP ToolKind Tools
execute execute bash (and any unknown kind)
read read read_skill, read_file
edit edit write_file, edit_file

Tool call identifiers

call_1, call_2, … numbered per process and never reset.

  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
# Reference: Agent Client Protocol surface

> Neutral description of what `mm -acp` exposes over JSON-RPC 2.0 on stdio, through `github.com/coder/acp-go-sdk` v0.13.5.

## Transport

| Aspect | Value |
|--------|-------|
| Input | stdin, one JSON-RPC message per line. |
| Output | stdout, JSON-RPC messages only. |
| Logs | stderr: banner, warnings, `[acp] …` lines, SDK logger. |
| Lifetime | Until the client closes stdin or the process context is cancelled. |

## `initialize` response

| Field | Value |
|-------|-------|
| `protocolVersion` | `1` |
| `agentCapabilities` | `loadSession: true` when `sessions.enabled` is set, absent (false) otherwise. Everything else absent (false): no image or audio prompts, no HTTP MCP. |
| `agentInfo.name` | `bob` |
| `agentInfo.title` | `Bob (bash-first agent)` |
| `agentInfo.version` | `0.11.0` |
| `authMethods` | `[]` |

## Methods

| Method | Behaviour |
|--------|-----------|
| `initialize` | Logs the client name and protocol version on stderr; returns the values above. |
| `authenticate` | Returns an empty response. |
| `session/new` | Creates a session with a time-sortable id (`20260916-101530-4f2a9c1e`) and the request's `cwd`, a fresh history holding the system prompt, and an empty allow-always set. `mcpServers` are counted in the log and ignored. Once the response is written, sends an `available_commands_update` notification listing the slash commands (see below). With `sessions.enabled`, writes `<cwd>/<sessions.dir>/<id>.json` at once and rewrites it after every turn, `/new` and `/compact`. |
| `session/prompt` | Runs one generation on the session's history, or executes a slash command without consulting the model. Turns are serialised process-wide. |
| `session/cancel` | Cancels the turn in flight for that session. |
| `session/load` | With `sessions.enabled`: reads `<cwd>/<sessions.dir>/<sessionId>.json`, replaces its system prompt by the current one, registers the session (empty allow-always set), sends the replay (below) then an `available_commands_update`, and answers `{}`. Unknown id or unreadable file: `invalid params` carrying the `sessionId` and the error. Without `sessions.enabled`: `method not found`. |
| `session/set_mode`, `session/set_config_option`, `session/list`, `session/resume`, `session/close`, `logout` | `method not found`. |

## Skills as commands

ACP has no notion of a skill. A prompt carries `text`, `image`, `audio`, `resource` or `resource_link` — the last being what an editor's `@` file picker sends — and there is nothing for "run this procedure".

`AvailableCommand` is the slot, and the protocol's own examples say so: its documentation offers `create_plan` and `research_codebase`, which are procedures rather than session commands. So each skill of the catalogue is advertised there, and the editor lists it in its own `/` menu:

```
/compact                                              Compress the history now, keeping the last turns
/new                                                  Clear the history and start a new session
/bg-jobs          [input: parameters, if …]           Start, wait on, inspect and stop a long-running background job…
/greetings        [input: parameters, if …]           Greet somebody with a festive message.
/vulcan-salute    [input: parameters, if …]           Send a vulcan salute to somebody.
```

The `input.hint` is what carries the parameters: the protocol says "all text that was typed after the command name is provided as input", which is exactly what a skill takes.

| Aspect | Behaviour |
|---|---|
| What happens on `/greetings Bob` | **Not intercepted.** It is rewritten as the `$greetings Bob` notation and the turn runs normally, so the model still makes the `read_skill` call — the loop detector, the permission gate and the `📖 N skill(s)` recap all still see it, and `mm` never loads a skill behind the model's back. |
| A real command wins | `session/prompt` tries the interception first, so a skill called `compact` or `new` can never shadow one. Such a skill is **not advertised** either, and the start-up banner says so — otherwise it would be silently unreachable from the menu. |
| An unknown `/name` | Left exactly as typed and handed to the model, as any arbitrary slash text was before. |
| Typing `$greetings Bob` by hand | Works too, in the editor as in the terminal: the notation is resolved in `session/prompt` whether the menu wrote it or the user did. |

Note the invariant changed with this: it used to be "only advertise what `session/prompt` intercepts", and it is now "only advertise what this front end **handles**" — rewriting is handling.

## `session/load` replay

Sent before the response, one notification per element of the stored history, in order. The system prompt is not sent.

| Stored element | Notification |
|----------------|--------------|
| User message | `user_message_chunk` with its text. |
| Compression summary (user-role message marked by `/compact`) | `agent_message_chunk` with `🗜️ ` followed by the summary. |
| Model text | `agent_message_chunk`. |
| Tool call with its recorded response | `tool_call` with `toolCallId` = the call's ref, `title` and `kind` as the live tool would give (`bash: <command>` execute, `read_file: <path>` read, `write_file`/`edit_file` edit, `read_skill: <name>` read), `status: completed`, `rawInput`, `rawOutput.output` and a `text` content holding the output. |
| Tool call without a recorded response | `tool_call` with `status: failed` and no content. |

No tool runs during a replay. Logged as `[acp] session <id> loaded from <dir>: N message(s) replayed`.

## `session/prompt`

| Aspect | Behaviour |
|--------|-----------|
| Unknown `sessionId` | `invalid params` error carrying the `sessionId`. |
| Prompt content | `text` blocks concatenated; a `resource_link` becomes `\n[attached file: <path without file://>]`; other block types are ignored. Then every `@path` in the text that names an existing file or directory under the session's `cwd` (or an absolute / `~` path) appends `\n[attached file: <absolute path>]` or `\n[attached directory: …]`, once per path, and is logged as `[acp] session <id>: attached <path>`. |
| Slash command | When the flattened text, trimmed, is exactly `/new` or `/compact`, no generation runs; the command answers with one `agent_message_chunk` and `stopReason: end_turn`. |
| `/new` | The session's history is reset to the system prompt alone, the engine's last input-token count is forgotten, the chunk is `🆕 New session: N message(s) forgotten.`. The session id, `cwd` and allow-always grants are kept. With `sessions.enabled`, the conversation so far is saved under a new id first (logged `previous conversation kept as <id>`; nothing saved when it was empty) and the session's own file is rewritten with the fresh history. Logged as `[acp] session <id>: new session, N message(s) forgotten`. |
| `/compact` | The history is compressed with the `context` settings, forced (the threshold is ignored, `keepLastTurns` is honoured). The chunk is `🗜️ compressed N messages → 1 summary + N kept (… tokens, …s)`, or `🗜️ nothing to compact: N message(s), no turn older than the last K` (history unchanged), or `[compact: failed, history kept: <explanation>]` (history unchanged). The token count is forgotten only when the history changed. Waits for a running turn: turns are serialised. Logged as `[acp] session <id>: /compact — <line>`. |
| History | The full history returned by the engine is kept, even after a failure or a cancellation; a question with no answer at all is removed. |
| Success | `stopReason: end_turn`. |
| Cancelled | `stopReason: cancelled`. |
| Failure | `internal error` with `error: <one-line explanation>`. |
| Log line | `[acp] turn done: N command(s) · N file op(s) · N skill(s)` on stderr. |

## `session/update` notifications sent

| Update | When |
|--------|------|
| `available_commands_update` | Once per session, right after the `session/new` response has been written (the editor must know the session id first, or it drops the update). Lists what this front end **handles**: the slash commands `session/prompt` intercepts, then one command per **skill**. Also sent at the end of a `session/load` replay. |
| `agent_message_chunk` (text) | Each streamed chunk of the model's answer, whitespace preserved; also the one-line confirmation of a slash command. |
| `tool_call` status `pending` | Before a tool runs; carries `title`, `kind`, `rawInput`, and `locations` with the absolute path when a file is involved. |
| `tool_call_update` status `in_progress` | Permission granted, tool executing. |
| `tool_call_update` status `completed` or `failed` | Tool finished; `content` is a `diff` for file changes, else a `text` block with the output; `rawOutput.output` always carries the output. |

## Available commands

| `name` | `description` | `input` |
|--------|---------------|---------|
| `new` | `Clear the history and start a new session` | none |
| `compact` | `Compress the history now, keeping the last turns` | none |
| one per skill | the skill's `description` | `{ "hint": "parameters, if this skill needs any" }` |

Clients display the name with a leading slash and send it back as the text of a `session/prompt`. The two commands above are intercepted there; a skill's command is rewritten into the `$skill` notation and the turn runs — see [Skills as commands](#skills-as-commands).

## `session/request_permission` requests sent

Sent before `bash`, `read_file`, `write_file` and `edit_file` run; never for `read_skill`.

| Option id | Kind | Effect |
|-----------|------|--------|
| `allow` | `allow_once` | Runs the tool. |
| `allow_always` | `allow_always` | Runs the tool and skips the dialog for that tool name for the rest of the session. |
| `reject` | `reject_once` | Does not run; the model receives `[command rejected by the user]`. |

A transport error or a `cancelled` outcome counts as a rejection.

## Tool kinds

| `mm` kind | ACP `ToolKind` | Tools |
|-----------|----------------|-------|
| `execute` | `execute` | `bash` (and any unknown kind) |
| `read` | `read` | `read_skill`, `read_file` |
| `edit` | `edit` | `write_file`, `edit_file` |

## Tool call identifiers

`call_1`, `call_2`, … numbered per process and never reset.