Reference: the configuration file
- Format version: 1
- Issue: #4 — Spécifier le format du fichier de configuration YAML
- Schema: schema/config.schema.json (JSON Schema 2020-12), checked by
go test ./schema - Example: examples/test-agents.yaml
One YAML file describes the agents Sidekicks can talk to, the channels created at startup, and preferences. Conversations, messages and ACP sessions are not in it (see Architecture).
The application reads it with the Go package internal/config (milestone M1): validation, defaults, paths, the default file, reloading on every save.
Location
The file is looked up in this order; the first one that applies wins:
| # | Source | Example |
|---|---|---|
| 1 | --config <path> command-line flag |
sidekicks --config ~/agents/team.yaml |
| 2 | SIDEKICKS_CONFIG environment variable |
SIDEKICKS_CONFIG=~/agents/team.yaml |
| 3 | the last file opened from the application (Settings → Open… or Recent…), kept in <user config dir>/sidekicks/preferences.json |
— |
| 4 | the default location: <user config dir>/sidekicks/config.yaml |
macOS: ~/Library/Application Support/sidekicks/config.yaml, Linux: ~/.config/sidekicks/config.yaml |
<user config dir> is Go's os.UserConfigDir(). When the default file does not exist, Sidekicks creates it with comments explaining each key (issue #9).
Paths
- A relative path is resolved against the folder of the configuration file, never against the current directory. A folder holding a configuration and its agents can therefore be moved or versioned with git as a whole.
~at the start of a path is the user's home folder.command: a bare name (mm,docker) is looked up inPATH; anything containing/is a path, resolved as above.argsare passed as they are, without rewriting: the agent process starts in itsworkspace, so arguments relative to the workspace work.
Keys
Top level
| Key | Type | Required | Default | Meaning |
|---|---|---|---|---|
version |
integer | yes | — | Format version; only 1 exists. A later format increments it, so Sidekicks can migrate or refuse an old file explicitly. |
ui |
object | no | Interface preferences. | |
limits |
object | no | Resource limits. | |
agents |
list of agents | yes | The agents available with @name. May be empty. |
|
channels |
list of channels | no | [] |
Channels created at startup when they do not exist yet. |
Unknown keys are errors, at every level: a misspelt comand: must be reported, not silently ignored.
ui
| Key | Values | Default | Meaning |
|---|---|---|---|
theme |
system, light, dark |
system |
Colour theme; system follows the operating system. |
language |
en, fr |
en |
Language of the interface: its labels and the configuration error messages. |
limits
| Key | Type | Default | Meaning |
|---|---|---|---|
maxRunningAgents |
integer ≥ 1 | 2 |
Agents running at the same time. Each local agent keeps a model in memory; the default fits a 32 GB laptop with small models. |
stopIdleAgentsAfter |
Go duration (30s, 10m, 1h30m), or 0 (number or text) |
10m |
Stops an agent unused for this long, to free its memory. 0 never stops it. |
An agent
| Key | Type | Required | Default | Meaning |
|---|---|---|---|---|
name |
identifier | yes | Lowercase letters, digits, -, _; starts with a letter; 32 characters at most. Used as @name and /name:command. Unique. |
|
title |
text | no | name |
Name shown in the interface. |
icon |
text (an emoji or a letter) | no | Shown next to the agent. An emoji is shown as it is; otherwise its first letter is shown in a coloured circle (the colour depends on name). Without icon, the circle holds the first letter of title. |
|
description |
text | no | One sentence shown in the @ list. |
|
command |
text | yes | Program serving ACP on stdio. | |
args |
list of text | no | [] |
Its arguments. |
env |
map text → text | no | Extra environment variables. | |
afterStop |
list of text, at least one | no | Command run each time the agent's process ends — stopped, crashed, stopped to make room, or Sidekicks quitting: the program (a bare name is looked up in PATH, a path is relative to this file), then its arguments. It runs in workspace with env, for 30 s at most; its output goes to the agent's log; Sidekicks waits for it before quitting. A program that cannot be found is a warning. Example: [../scripts/agents.sh, unload, alice] to unload the model. |
|
workspace |
path | no | the configuration folder | The agent's folder: the process starts there and it is the cwd of its ACP sessions. mm reads AGENTS.md and skills/ from it. |
protect |
list of text | no | Files of the agent's folder the interface (Files tab) may neither change nor delete: paths relative to the folder, or patterns where * and ? match within a name and ** any number of folders ([AGENTS.md, "skills/**"]). A folder holding a protected file cannot be deleted. Always protected, when they are in its folder: AGENTS.md, skills/**, the agent's configuration file (the argument after --config, -config or serve acp in args), and the files runtime.lifecycle builds the container or the sandbox from — the Compose file (after -f/--file, or compose.yaml… without it), the Dockerfile of the lifecycle folder for a container, the sbx environment file (sbxenv.yaml or the one named). Only the configuration lifts a protection; the agent itself, the terminal or the Finder are not bound by it. |
|
runtime |
object | no | {type: local} |
Where the agent runs (see below). |
runtime
The runtime tells Sidekicks how to reach the agent's files and terminal (Agents panel, issue #28). It does not change how the agent is started: command and args do that.
type |
Extra keys | Meaning |
|---|---|---|
local |
none | The agent runs on this computer; workspace is a local folder. |
container |
container (required), workspacePath, lifecycle |
The agent runs in a Docker container; files and terminal go through docker exec. A stopped container is started before the agent. |
sbx |
sandbox (required), workspacePath, lifecycle |
The agent runs in a Docker sandbox (sbx); files and terminal go through sbx exec. |
workspacePath is an absolute path inside the container or the sandbox; it defaults to workspace (the same path, as with a bind mount). Behaviour of each runtime: workspaces.
runtime.lifecycle (container and sbx)
Optional commands that manage the agent's container or sandbox (issues #47 and #48). Each command is a list: the program (a bare name is looked up in PATH, a path is relative to this file), then its arguments.
| Key | Type | Default | Meaning |
|---|---|---|---|
folder |
text | workspace |
Folder the commands run in. Relative to this file; ~ is the home folder. |
create |
list of text, at least one | Creates the container or the sandbox. Before starting the agent, Sidekicks checks that it exists (docker container inspect, sbx ls -q); when it does not, it runs this command, then checks again. |
|
stop |
list of text, at least one | Stops it. Offered as ■ Stop the container / ■ Stop the sandbox in the Agents panel; the agent is stopped first. | |
remove |
list of text, at least one | Removes it. Offered as 🗑 Remove the container / 🗑 Remove the sandbox in the Agents panel, after a confirmation; the agent is stopped first. |
A stopped sandbox needs nothing: sbx exec starts it. A stopped container is started with docker start, even without lifecycle. The commands get no input (one that asks a question fails), run with the agent's env, and what they print goes to the agent's log, prefixed with sandbox: . A failing create puts the agent in the error state with the reason.
runtime:
type: sbx
sandbox: milo-sbx # the name: of the sbxenv.yaml
lifecycle:
folder: /Users/me/agents/milo # where sbxenv.yaml is
create: [sbx, env, create, --force]
stop: [sbx, stop, milo-sbx]
remove: [sbx, env, rm, --force]
With an environment file not called sbxenv.yaml, name it: [sbx, env, create, dev.yaml, --force], [sbx, env, rm, dev.yaml, --force]. Without an environment file, use the plain commands, e.g. remove: [sbx, rm, milo-sbx, --force].
A container of a Docker Compose service (what the assistant writes when a compose.yaml is dropped):
runtime:
type: container
container: team-scout-1 # container_name, or <project>-<service>-1
lifecycle:
folder: /Users/me/agents/team # where compose.yaml is
create: [docker, compose, -f, compose.yaml, up, -d, --wait, scout]
stop: [docker, compose, -f, compose.yaml, stop, scout]
remove: [docker, compose, -f, compose.yaml, rm, --stop, --force, scout]
up --wait returns once the service runs (healthy, if it has a health check); rm --stop --force scout removes this service's container only, not the other services as down would. Without Compose: create: [docker, run, -d, --name, scout-box, -v, "/Users/me/agents/scout:/Users/me/agents/scout", my-image].
A channel
| Key | Type | Required | Meaning |
|---|---|---|---|
name |
text, 1–64 characters | yes | Shown as #name. Unique. May contain accents and spaces. |
description |
text | no | Shown when hovering the channel's name, and in an empty channel. |
agents |
list of agent names | no | Members of the channel. A listed agent that is not a member yet is added at startup and when the file is reloaded; removing it from the list removes nothing. No duplicates. |
A channel declared here is created at startup, or when the file is reloaded, if no channel has this name yet (whatever the case); after that it lives in the application like any other channel, and can be renamed or deleted there. Removing it from the file does not delete it (deleting history must be an explicit user action). Each configuration file has its own channels and history (where it is kept).
What the "Add an agent" assistant writes
The assistant appends one entry at the end of agents; the rest of the file, comments included, is kept. Nothing is written when the result would not be valid.
| Field of the assistant | Written as |
|---|---|
| Name used with @, Name shown, Icon, What it does | name, title, icon, description (empty fields left out) |
| Agent's folder | workspace, as typed (~ and relative paths are resolved like any workspace) |
| On this computer | command: <program>, args: [<arguments>] |
| In a Docker container | command: docker, args: [exec, -i, <container>, <program>, <arguments>], runtime: {type: container, container: <container>} |
| In an sbx sandbox | command: sbx, args: [exec, -i, <sandbox>, <program>, <arguments>], runtime: {type: sbx, sandbox: <sandbox>} |
| Folder the commands run in, Command that creates / stops / removes the sandbox (sbx only) | runtime.lifecycle: {folder, create, stop, remove}, each command split like the arguments; left out when no command is given |
The arguments are split like a shell does for simple cases: spaces separate, single or double quotes group. The program is split the same way, unless it is a path (it starts with / or ~): docker agent gives command: docker and agent as the first argument.
When the agent's configuration file is given (dropped or chosen, wherever the agent runs: an sbx sandbox shares host folders at the same path, and a container must mount the file's folder at the same path), the agent's folder is the file's folder, and the arguments use the file's absolute path:
| Program | Arguments |
|---|---|
mm |
--acp --config <file> |
docker-agent |
serve acp <file> |
docker agent |
serve acp <file> (written command: docker, args: [agent, serve, acp, <file>]) |
| any other | unchanged: typed by the user |
Rules checked by the loader, not by the schema
JSON Schema cannot express them; the configuration loader (issue #9) checks them and reports them like schema errors:
- agent names are unique;
- channel names are unique;
- every name in a channel's
agentsis the name of an agent; commandexists (inPATH, or as a file) — reported as a warning, since an agent may be installed later.
Error messages
Every error names the line and the key, in the language of the interface, and says how to fix it. Examples with the interface in English (the default):
| Problem | Message |
|---|---|
name: Alice Smith |
line 12 — agents[0].name: "Alice Smith" is not a valid name: lowercase letters, digits, "-" or "_", no space, starting with a letter (e.g. "alice") |
comand: mm |
line 14 — agents[0].comand: unknown key "comand"; did you mean "command"? |
command missing |
line 3 — agents[0]: the key "command" is missing |
Full example
version: 1
ui:
theme: system
language: fr
limits:
maxRunningAgents: 2
stopIdleAgentsAfter: 10m
agents:
- name: alice
title: Alice (assistante)
icon: "👩"
description: Questions simples, sans outils
command: mm
args: ["-acp", "-config", "agent.yaml"]
workspace: ./agents/alice
- name: garfield
title: Garfield (traducteur)
icon: "🐮"
command: mm
args: ["-acp", "-config", "agent.yaml"]
workspace: ./agents/garfield
- name: translator-sbx
title: Traducteur (sandbox)
command: sbx
args: ["exec", "-i", "mm-translator", "mm", "-acp"]
workspace: ./agents/translator/workspace
runtime:
type: sbx
sandbox: mm-translator
channels:
- name: général
agents: [alice]
- name: traductions
agents: [garfield]
sbx exec -i <sandbox> <program> runs the agent inside the sandbox; sbx mounts the host folder at the same path, so workspace is also the folder seen inside it. This example is also a test fixture (schema/testdata/valid/spec-example.yaml).
Go package schema
git.rickub.com/bots-garden/sidekicks/schema embeds the JSON Schema and validates documents against it.
| Identifier | Signature | Description |
|---|---|---|
ConfigSchemaURL |
const string |
The schema's $id: https://git.rickub.com/bots-garden/sidekicks/schema/config.schema.json. |
ConfigSchema |
func() []byte |
A copy of the JSON Schema document. |
ValidateConfig |
func(document []byte) error |
nil if the YAML document matches the schema; a YAML syntax error if it cannot be parsed; a *jsonschema.ValidationError listing every violation otherwise. |
if err := schema.ValidateConfig(content); err != nil {
fmt.Println("invalid configuration:", err)
}
A violation as returned by ValidateConfig:
jsonschema validation failed with 'https://git.rickub.com/bots-garden/sidekicks/schema/config.schema.json#'
- at '/agents/0': additional properties 'comand' not allowed
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 |
|