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
|
# Reference: agent states and events
## Agent states
| State | Shown as | Meaning |
| --- | --- | --- |
| `stopped` | stopped | No process. The initial state of every agent. |
| `starting` | starting… | The process is launched and the ACP handshake (`initialize`) is in progress. |
| `ready` | ready | Running and idle. |
| `busy` | busy | Running and used by at least one conversation. |
| `error` | error | The start failed, or the process exited on its own (crash). The status carries the reason, followed by the agent's last stderr lines for a failed start. |
`outdated` (shown as "restart to apply changes") is set on a running agent whose definition changed in the configuration; a restart applies it.
## Transitions
| From | Event | To |
| --- | --- | --- |
| `stopped`, `error` | start requested (▶ in the Agents panel, or first use) | `starting` |
| `starting` | handshake succeeds | `ready` |
| `starting` | handshake fails, protocol other than ACP v1, timeout (30 s) | `error` |
| `ready` | used | `busy` |
| `busy` | last user done | `ready` |
| `ready`, `busy` | stop requested (■), application quits, agent removed from the configuration | `stopped` |
| `ready` | unused for `limits.stopIdleAgentsAfter` (checked every 30 s) | `stopped` |
| `ready` | another agent must start and this one is the least recently used idle agent | `stopped` |
| `ready`, `busy` | the process exits on its own | `error` (never restarted automatically) |
| `starting` | stop requested | `stopped` once the start ends |
When `limits.maxRunningAgents` agents are running and all are `busy`, a start is refused with the message `too many agents running: N agent(s) busy (…), the limit is N`.
Stopping an agent closes its stdin, waits 5 seconds, then kills its process group (the agent and what it started, such as `mm` under `scripts/agents.sh`).
## Events from Go to the interface
| Event | Payload | Sent when |
| --- | --- | --- |
| `config:changed` | `{path, valid, problems[]}` | The configuration file was saved and reloaded (valid or not). |
| `agent:state` | `{agent, state, error?}` | An agent changes state or its definition changes. |
| `agent:log` | `{agent, line}` | An agent writes a line on its standard error. |
The Go names and types are in `internal/events`; the TypeScript constants and types are generated in `frontend/bindings/events` and `frontend/bindings/…/internal/events`.
## Services called by the interface
| Service | Method | Effect |
| --- | --- | --- |
| `AgentService` | `List()` | Every agent with `name, title, icon, description, state, error, outdated`, in configuration order. |
| | `Start(name)`, `Stop(name)`, `Restart(name)` | As in the transitions above; an error is returned to the interface. |
| | `Logs(name)` | The last 200 stderr lines of the agent. |
| `ConfigService` | `Current()` | `path, source, created, valid, problems[], warnings[], theme, language, channels[]`. |
## Conversations (milestone M2)
One conversation per agent, kept in memory until the application quits or another configuration is opened.
### Messages
| Field | Meaning |
| --- | --- |
| `id`, `agent`, `createdAt` | identity |
| `role` | `user`, `agent` or `system` |
| `parts` | `text` (Markdown), `thought` (reasoning), `tool` (a tool call), `plan` (plan entries) |
| `done` | false while an agent message streams |
| `queued` | a user message waiting for the current answer |
| `stopReason` | how an agent message ended: `end_turn`, `cancelled`, `max_tokens`, `max_turn_requests`, `refusal` |
| `error` | why an agent message ended without a stop reason (the agent's own explanation), or `cancelled` for a queued message dropped by Stop |
| `code` | system messages: `agent-restarted` (new session, the agent forgot the beginning), `agent-unavailable` (could not start or reach it; `error` says why) |
A tool call has `id, title, kind` (`read, edit, delete, move, search, execute, think, fetch, other`), `status` (`pending, in_progress, completed, failed`), `input` (compact JSON), `output`, `locations`, `diffs` (`path, oldText, newText, newFile`).
### Events
| Event | Payload | Sent when |
| --- | --- | --- |
| `message:updated` | `{agent, message}` | a message is added, or finished |
| `message:delta` | `{agent, messageId, index, kind, text?, part?}` | `text` is appended to the part at `index` (text, thought), or `part` replaces it (tool, plan) |
| `turn:changed` | `{agent, busy, queued}` | the agent starts or stops answering; `queued` messages wait |
| `permission:requested` | `{id, agent, messageId, toolCall, options[], rejections}` | the agent asks to run a tool; `rejections` counts the refusals already given in this turn |
| `permission:resolved` | `{id, agent}` | the request was answered, or cancelled by Stop |
| `usage:changed` | `{agent, usage: {used, size}}` | the agent reported its context window use (`size` is 0 when unknown) |
### `ChatService`
| Method | Effect |
| --- | --- |
| `Send(agent, text)` | Adds a user message and returns its id; starts the agent if needed; queues the message if the agent is answering. Empty text and unknown agents are errors. |
| `Cancel(agent)` | Stops the current answer (`session/cancel`), answers pending permission requests "cancelled", drops the queued messages. |
| `AnswerPermission(id, optionId)` | Answers a pending request with one of its options. |
| `Conversation(agent)` | The whole conversation: `messages, busy, queued, usage, commands, permissions`. |
### Settings methods of `ConfigService`
| Method | Effect |
| --- | --- |
| `Content()` | The text of the configuration file. |
| `Validate(text)` | The problems and warnings of `text`, with `line, column, message, warning`, without saving. |
| `Save(text)` | Writes `text` if it is valid; otherwise returns its problems and writes nothing. |
| `Schema()` | The configuration JSON Schema. |
| `Open(path)` | Stops every agent, forgets the conversations, loads and watches `path`, remembers it. |
| `Choose()` | Shows the native file dialog and opens the chosen file; returns `""` when cancelled. |
| `Recent()` | The configuration files opened from the application, most recent first (8 at most). |
### Stopping an agent
The agent's stdin is closed; after 5 s the agent and its children get SIGTERM (time for `scripts/agents.sh` to unload the model); after 3 more seconds, SIGKILL.
|