ScuttleBot
feat: scuttlectl topology/config/bot commands, bootstrap.md refresh (#104, #101, #102) scuttlectl: add topology (list/provision/drop), config (show/history), and bot (list) subcommands. All backed by existing API endpoints. bootstrap.md: update bot count to 10 (add sentinel + steward), expand API endpoint table from 13 to 35+ with scope annotations, document API key system, add scuttlectl topology/config/bot/api-key commands, update persistence table, update "Adding a New Bot" checklist.
Commit
c77ae38a39b0ed67f58f76e2cd663faff5a011267b67c48c1d3911a7c09b939f
Parent
d11e165f13fc316…
1 file changed
+78
-30
+78
-30
| --- bootstrap.md | ||
| +++ bootstrap.md | ||
| @@ -157,24 +157,26 @@ | ||
| 157 | 157 | - `+v` (voice) — trusted worker agents |
| 158 | 158 | - no mode — standard agents |
| 159 | 159 | |
| 160 | 160 | ### Built-in bots |
| 161 | 161 | |
| 162 | -All 8 bots are implemented. Enabled/configured via the web UI or `scuttlectl`. The manager (`internal/bots/manager/`) starts/stops them dynamically when policies change. | |
| 162 | +All 10 bots are implemented. Enabled/configured via the web UI or `scuttlectl bot list`. The manager (`internal/bots/manager/`) starts/stops them dynamically when policies change. All bots set `+B` (bot) user mode on connect and auto-accept INVITE. | |
| 163 | 163 | |
| 164 | 164 | | Bot | Nick | Role | |
| 165 | 165 | |-----|------|------| |
| 166 | 166 | | `auditbot` | auditbot | Immutable append-only audit trail of agent actions and credential events | |
| 167 | 167 | | `herald` | herald | Routes inbound webhook events to IRC channels | |
| 168 | 168 | | `oracle` | oracle | On-demand channel summarization via DM — calls any OpenAI-compatible LLM | |
| 169 | 169 | | `scribe` | scribe | Structured message logging to rotating files (jsonl/csv/text) | |
| 170 | -| `scroll` | scroll | History replay to PM on request | | |
| 171 | -| `snitch` | snitch | Flood and join/part cycling detection — alerts operators via DM or channel | | |
| 170 | +| `scroll` | scroll | History replay to PM on request (`replay #channel [format=toon]`) | | |
| 171 | +| `sentinel` | sentinel | LLM-powered channel observer — detects policy violations, posts structured incident reports to mod channel. Never takes enforcement action. | | |
| 172 | +| `snitch` | snitch | Flood and join/part cycling detection, MONITOR-based presence tracking, away-notify alerts | | |
| 173 | +| `steward` | steward | Acts on sentinel incident reports — issues warnings, mutes (extended ban `m:`), or kicks based on severity | | |
| 172 | 174 | | `systembot` | systembot | Logs IRC system events (joins, parts, quits, mode changes) | |
| 173 | -| `warden` | warden | Channel moderation — warn → mute → kick on flood | | |
| 175 | +| `warden` | warden | Channel moderation — warn → mute (extended ban) → kick on flood | | |
| 174 | 176 | |
| 175 | -Oracle reads history from scribe's log files (pointed at the same dir). Configure `api_key_env` to the name of the env var holding the API key (e.g. `ORACLE_OPENAI_API_KEY`), and `base_url` for non-OpenAI providers. | |
| 177 | +Oracle uses TOON format (`pkg/toon/`) for token-efficient LLM context. Scroll supports `format=toon` for compact replay output. Configure `api_key_env` to the name of the env var holding the API key (e.g. `ORACLE_OPENAI_API_KEY`), and `base_url` for non-OpenAI providers. | |
| 176 | 178 | |
| 177 | 179 | ### Scale |
| 178 | 180 | |
| 179 | 181 | Target: 100s to low 1000s of agents on a private network. Single Ergo instance handles this comfortably (documented up to 10k clients, 2k per channel). Ergo scales up (multi-core), not out — no horizontal clustering today. Federation is planned upstream but has no timeline; not a scuttlebot concern for now. |
| 180 | 182 | |
| @@ -186,11 +188,12 @@ | ||
| 186 | 188 | |------|------|-------| |
| 187 | 189 | | Agent registry | `data/ergo/registry.json` | Agent records + SASL credentials | |
| 188 | 190 | | Admin accounts | `data/ergo/admins.json` | bcrypt-hashed; created by `scuttlectl admin add` | |
| 189 | 191 | | Policies | `data/ergo/policies.json` | Bot config, agent policy, logging settings | |
| 190 | 192 | | Bot passwords | `data/ergo/bot_passwords.json` | Auto-generated SASL passwords for system bots | |
| 191 | -| API token | `data/ergo/api_token` | Bearer token for API auth; stable across restarts | | |
| 193 | +| API token | `data/ergo/api_token` | Legacy token; migrated to api_keys.json on first run | | |
| 194 | +| API keys | `data/ergo/api_keys.json` | Per-consumer tokens with scoped permissions (SHA-256 hashed) | | |
| 192 | 195 | | Ergo state | `data/ergo/ircd.db` | Ergo-native: accounts, channels, topics, history | |
| 193 | 196 | | scribe logs | `data/logs/scribe/` | Rotating log files (jsonl/csv/text); configurable | |
| 194 | 197 | |
| 195 | 198 | K8s / Docker: mount a PersistentVolume at `data/`. Ergo is single-instance — HA = fast pod restart with durable storage, not horizontal scaling. |
| 196 | 199 | |
| @@ -228,48 +231,79 @@ | ||
| 228 | 231 | `internal/api/` — two-mux pattern: |
| 229 | 232 | |
| 230 | 233 | - **Outer mux** (unauthenticated): `POST /login`, `GET /` (redirect), `GET /ui/` (web UI) |
| 231 | 234 | - **Inner mux** (`/v1/` routes): require `Authorization: Bearer <token>` header |
| 232 | 235 | |
| 233 | -The API token is a random hex string generated once at startup, persisted to `data/ergo/api_token`. | |
| 234 | - | |
| 235 | 236 | ### Auth |
| 236 | 237 | |
| 237 | -`POST /login` accepts `{username, password}` and returns `{token, username}`. The token is the shared server API token. Rate limited to 10 attempts per minute per IP. | |
| 238 | +API keys are per-consumer tokens with scoped permissions. Each key has a name, scopes, optional expiry, and last-used tracking. Scopes: `admin`, `agents`, `channels`, `chat`, `topology`, `bots`, `config`, `read`. The `admin` scope implies all others. | |
| 239 | + | |
| 240 | +`POST /login` accepts `{username, password}` and returns a 24h session token with admin scope. Rate limited to 10 attempts per minute per IP. | |
| 241 | + | |
| 242 | +On first run, the legacy `api_token` file is migrated into `api_keys.json` as the first admin-scope key. New keys are created via `POST /v1/api-keys`, `scuttlectl api-key create`, or the web UI settings tab. | |
| 238 | 243 | |
| 239 | -Admin accounts are managed via `scuttlectl admin` or the web UI settings → admin accounts card. First run auto-creates an `admin` account with a random password printed to the log. | |
| 244 | +Admin accounts managed via `scuttlectl admin` or web UI. First run auto-creates `admin` with a random password printed to the log. | |
| 240 | 245 | |
| 241 | 246 | ### Key endpoints |
| 242 | 247 | |
| 243 | -| Method | Path | Description | | |
| 244 | -|--------|------|-------------| | |
| 245 | -| `POST` | `/login` | Username/password login (unauthenticated) | | |
| 246 | -| `GET` | `/v1/status` | Server status | | |
| 247 | -| `GET` | `/v1/metrics` | Runtime metrics + bridge stats | | |
| 248 | -| `GET/PUT` | `/v1/settings/policies` | Bot config, agent policy, logging | | |
| 249 | -| `GET` | `/v1/agents` | List all registered agents | | |
| 250 | -| `POST` | `/v1/agents/register` | Register an agent | | |
| 251 | -| `POST` | `/v1/agents/{nick}/rotate` | Rotate credentials | | |
| 252 | -| `POST` | `/v1/agents/{nick}/revoke` | Revoke agent | | |
| 253 | -| `GET` | `/v1/channels` | List joined channels | | |
| 254 | -| `GET` | `/v1/channels/{ch}/stream` | SSE stream of channel messages | | |
| 255 | -| `GET/POST` | `/v1/admins` | List / add admin accounts | | |
| 256 | -| `DELETE` | `/v1/admins/{username}` | Remove admin | | |
| 257 | -| `PUT` | `/v1/admins/{username}/password` | Change password | | |
| 248 | +All `/v1/` endpoints require a Bearer token with the appropriate scope. | |
| 249 | + | |
| 250 | +| Method | Path | Scope | Description | | |
| 251 | +|--------|------|-------|-------------| | |
| 252 | +| `POST` | `/login` | — | Username/password login (unauthenticated) | | |
| 253 | +| `GET` | `/v1/status` | read | Server status | | |
| 254 | +| `GET` | `/v1/metrics` | read | Runtime metrics + bridge stats | | |
| 255 | +| `GET` | `/v1/settings` | read | Full settings (policies, TLS, bot commands) | | |
| 256 | +| `GET/PUT/PATCH` | `/v1/settings/policies` | admin | Bot config, agent policy, logging | | |
| 257 | +| `GET` | `/v1/agents` | agents | List all registered agents | | |
| 258 | +| `GET` | `/v1/agents/{nick}` | agents | Get single agent | | |
| 259 | +| `PATCH` | `/v1/agents/{nick}` | agents | Update agent | | |
| 260 | +| `POST` | `/v1/agents/register` | agents | Register an agent | | |
| 261 | +| `POST` | `/v1/agents/{nick}/rotate` | agents | Rotate credentials | | |
| 262 | +| `POST` | `/v1/agents/{nick}/adopt` | agents | Adopt existing IRC nick | | |
| 263 | +| `POST` | `/v1/agents/{nick}/revoke` | agents | Revoke agent credentials | | |
| 264 | +| `DELETE` | `/v1/agents/{nick}` | agents | Delete agent | | |
| 265 | +| `GET` | `/v1/channels` | channels | List joined channels | | |
| 266 | +| `POST` | `/v1/channels/{ch}/join` | channels | Join channel | | |
| 267 | +| `DELETE` | `/v1/channels/{ch}` | channels | Leave channel | | |
| 268 | +| `GET` | `/v1/channels/{ch}/messages` | channels | Get message history | | |
| 269 | +| `POST` | `/v1/channels/{ch}/messages` | chat | Send message | | |
| 270 | +| `POST` | `/v1/channels/{ch}/presence` | chat | Touch presence (keep web user visible) | | |
| 271 | +| `GET` | `/v1/channels/{ch}/users` | channels | User list with IRC modes | | |
| 272 | +| `GET` | `/v1/channels/{ch}/config` | channels | Per-channel display config | | |
| 273 | +| `PUT` | `/v1/channels/{ch}/config` | channels | Set display config (mirror detail, render mode) | | |
| 274 | +| `GET` | `/v1/channels/{ch}/stream` | channels | SSE stream (`?token=` query param auth) | | |
| 275 | +| `POST` | `/v1/channels` | topology | Provision channel via ChanServ | | |
| 276 | +| `DELETE` | `/v1/topology/channels/{ch}` | topology | Drop channel | | |
| 277 | +| `GET` | `/v1/topology` | topology | Channel types, static channels, active channels | | |
| 278 | +| `GET/PUT` | `/v1/config` | config | Server config read/write | | |
| 279 | +| `GET` | `/v1/config/history` | config | Config change history | | |
| 280 | +| `GET/POST` | `/v1/admins` | admin | List / add admin accounts | | |
| 281 | +| `DELETE` | `/v1/admins/{username}` | admin | Remove admin | | |
| 282 | +| `PUT` | `/v1/admins/{username}/password` | admin | Change password | | |
| 283 | +| `GET/POST` | `/v1/api-keys` | admin | List / create API keys | | |
| 284 | +| `DELETE` | `/v1/api-keys/{id}` | admin | Revoke API key | | |
| 285 | +| `GET/POST/PUT/DELETE` | `/v1/llm/backends[/{name}]` | bots | LLM backend CRUD | | |
| 286 | +| `GET` | `/v1/llm/backends/{name}/models` | bots | List models for backend | | |
| 287 | +| `POST` | `/v1/llm/discover` | bots | Discover models from provider | | |
| 288 | +| `POST` | `/v1/llm/complete` | bots | LLM completion proxy | | |
| 258 | 289 | |
| 259 | 290 | --- |
| 260 | 291 | |
| 261 | 292 | ## Adding a New Bot |
| 262 | 293 | |
| 263 | 294 | 1. Create `internal/bots/{name}/` package with a `Bot` struct and `Start(ctx context.Context) error` method |
| 264 | -2. Add a `BotSpec` config struct if the bot needs user-configurable settings | |
| 265 | -3. Register in `internal/bots/manager/manager.go`: | |
| 295 | +2. Set `+B` user mode on connect, handle INVITE for auto-join | |
| 296 | +3. Add a `BotSpec` config struct if the bot needs user-configurable settings | |
| 297 | +4. Register in `internal/bots/manager/manager.go`: | |
| 266 | 298 | - Add a case to `buildBot()` that constructs your bot from the spec config |
| 267 | 299 | - Add a `BehaviorConfig` entry to `defaultBehaviors` in `internal/api/policies.go` |
| 268 | -4. Add the UI config schema to `BEHAVIOR_SCHEMAS` in `internal/api/ui/index.html` | |
| 269 | -5. Write tests: bot logic, config parsing, edge cases. IRC connection can be skipped in unit tests. | |
| 270 | -6. Update this bootstrap | |
| 300 | +5. Add commands to `botCommands` map in `internal/api/policies.go` for the web UI command reference | |
| 301 | +6. Add the UI config schema to `BEHAVIOR_SCHEMAS` in `internal/api/ui/index.html` | |
| 302 | +7. Use `internal/bots/cmdparse/` for command routing if the bot accepts DM commands | |
| 303 | +8. Write tests: bot logic, config parsing, edge cases. IRC connection can be skipped in unit tests. | |
| 304 | +9. Update this bootstrap | |
| 271 | 305 | |
| 272 | 306 | No separate registration file or global registry. The manager builds bots by ID from the `BotSpec`. Bots satisfy the `bot` interface (unexported in manager package): |
| 273 | 307 | |
| 274 | 308 | ```go |
| 275 | 309 | type bot interface { |
| @@ -315,14 +349,28 @@ | ||
| 315 | 349 | go build ./cmd/scuttlectl # build CLI |
| 316 | 350 | go test ./... # run all tests |
| 317 | 351 | golangci-lint run # lint |
| 318 | 352 | |
| 319 | 353 | # Admin CLI |
| 354 | +scuttlectl status # server health | |
| 320 | 355 | scuttlectl admin list # list admin accounts |
| 321 | 356 | scuttlectl admin add alice # add admin (prompts for password) |
| 322 | 357 | scuttlectl admin passwd alice # change password |
| 323 | 358 | scuttlectl admin remove alice # remove admin |
| 359 | +scuttlectl api-key list # list API keys | |
| 360 | +scuttlectl api-key create --name "relay" --scopes chat,channels | |
| 361 | +scuttlectl api-key revoke <id> # revoke key | |
| 362 | +scuttlectl topology list # show channel types + static channels | |
| 363 | +scuttlectl topology provision #channel # create channel | |
| 364 | +scuttlectl topology drop #channel # remove channel | |
| 365 | +scuttlectl config show # dump config JSON | |
| 366 | +scuttlectl config history # config change history | |
| 367 | +scuttlectl bot list # show system bot status | |
| 368 | +scuttlectl agent list # list agents | |
| 369 | +scuttlectl agent register <nick> --type worker --channels #fleet | |
| 370 | +scuttlectl agent rotate <nick> # rotate credentials | |
| 371 | +scuttlectl backend list # LLM backends | |
| 324 | 372 | |
| 325 | 373 | # Docker |
| 326 | 374 | docker compose -f deploy/compose/docker-compose.yml up |
| 327 | 375 | ``` |
| 328 | 376 | |
| 329 | 377 |
| --- bootstrap.md | |
| +++ bootstrap.md | |
| @@ -157,24 +157,26 @@ | |
| 157 | - `+v` (voice) — trusted worker agents |
| 158 | - no mode — standard agents |
| 159 | |
| 160 | ### Built-in bots |
| 161 | |
| 162 | All 8 bots are implemented. Enabled/configured via the web UI or `scuttlectl`. The manager (`internal/bots/manager/`) starts/stops them dynamically when policies change. |
| 163 | |
| 164 | | Bot | Nick | Role | |
| 165 | |-----|------|------| |
| 166 | | `auditbot` | auditbot | Immutable append-only audit trail of agent actions and credential events | |
| 167 | | `herald` | herald | Routes inbound webhook events to IRC channels | |
| 168 | | `oracle` | oracle | On-demand channel summarization via DM — calls any OpenAI-compatible LLM | |
| 169 | | `scribe` | scribe | Structured message logging to rotating files (jsonl/csv/text) | |
| 170 | | `scroll` | scroll | History replay to PM on request | |
| 171 | | `snitch` | snitch | Flood and join/part cycling detection — alerts operators via DM or channel | |
| 172 | | `systembot` | systembot | Logs IRC system events (joins, parts, quits, mode changes) | |
| 173 | | `warden` | warden | Channel moderation — warn → mute → kick on flood | |
| 174 | |
| 175 | Oracle reads history from scribe's log files (pointed at the same dir). Configure `api_key_env` to the name of the env var holding the API key (e.g. `ORACLE_OPENAI_API_KEY`), and `base_url` for non-OpenAI providers. |
| 176 | |
| 177 | ### Scale |
| 178 | |
| 179 | Target: 100s to low 1000s of agents on a private network. Single Ergo instance handles this comfortably (documented up to 10k clients, 2k per channel). Ergo scales up (multi-core), not out — no horizontal clustering today. Federation is planned upstream but has no timeline; not a scuttlebot concern for now. |
| 180 | |
| @@ -186,11 +188,12 @@ | |
| 186 | |------|------|-------| |
| 187 | | Agent registry | `data/ergo/registry.json` | Agent records + SASL credentials | |
| 188 | | Admin accounts | `data/ergo/admins.json` | bcrypt-hashed; created by `scuttlectl admin add` | |
| 189 | | Policies | `data/ergo/policies.json` | Bot config, agent policy, logging settings | |
| 190 | | Bot passwords | `data/ergo/bot_passwords.json` | Auto-generated SASL passwords for system bots | |
| 191 | | API token | `data/ergo/api_token` | Bearer token for API auth; stable across restarts | |
| 192 | | Ergo state | `data/ergo/ircd.db` | Ergo-native: accounts, channels, topics, history | |
| 193 | | scribe logs | `data/logs/scribe/` | Rotating log files (jsonl/csv/text); configurable | |
| 194 | |
| 195 | K8s / Docker: mount a PersistentVolume at `data/`. Ergo is single-instance — HA = fast pod restart with durable storage, not horizontal scaling. |
| 196 | |
| @@ -228,48 +231,79 @@ | |
| 228 | `internal/api/` — two-mux pattern: |
| 229 | |
| 230 | - **Outer mux** (unauthenticated): `POST /login`, `GET /` (redirect), `GET /ui/` (web UI) |
| 231 | - **Inner mux** (`/v1/` routes): require `Authorization: Bearer <token>` header |
| 232 | |
| 233 | The API token is a random hex string generated once at startup, persisted to `data/ergo/api_token`. |
| 234 | |
| 235 | ### Auth |
| 236 | |
| 237 | `POST /login` accepts `{username, password}` and returns `{token, username}`. The token is the shared server API token. Rate limited to 10 attempts per minute per IP. |
| 238 | |
| 239 | Admin accounts are managed via `scuttlectl admin` or the web UI settings → admin accounts card. First run auto-creates an `admin` account with a random password printed to the log. |
| 240 | |
| 241 | ### Key endpoints |
| 242 | |
| 243 | | Method | Path | Description | |
| 244 | |--------|------|-------------| |
| 245 | | `POST` | `/login` | Username/password login (unauthenticated) | |
| 246 | | `GET` | `/v1/status` | Server status | |
| 247 | | `GET` | `/v1/metrics` | Runtime metrics + bridge stats | |
| 248 | | `GET/PUT` | `/v1/settings/policies` | Bot config, agent policy, logging | |
| 249 | | `GET` | `/v1/agents` | List all registered agents | |
| 250 | | `POST` | `/v1/agents/register` | Register an agent | |
| 251 | | `POST` | `/v1/agents/{nick}/rotate` | Rotate credentials | |
| 252 | | `POST` | `/v1/agents/{nick}/revoke` | Revoke agent | |
| 253 | | `GET` | `/v1/channels` | List joined channels | |
| 254 | | `GET` | `/v1/channels/{ch}/stream` | SSE stream of channel messages | |
| 255 | | `GET/POST` | `/v1/admins` | List / add admin accounts | |
| 256 | | `DELETE` | `/v1/admins/{username}` | Remove admin | |
| 257 | | `PUT` | `/v1/admins/{username}/password` | Change password | |
| 258 | |
| 259 | --- |
| 260 | |
| 261 | ## Adding a New Bot |
| 262 | |
| 263 | 1. Create `internal/bots/{name}/` package with a `Bot` struct and `Start(ctx context.Context) error` method |
| 264 | 2. Add a `BotSpec` config struct if the bot needs user-configurable settings |
| 265 | 3. Register in `internal/bots/manager/manager.go`: |
| 266 | - Add a case to `buildBot()` that constructs your bot from the spec config |
| 267 | - Add a `BehaviorConfig` entry to `defaultBehaviors` in `internal/api/policies.go` |
| 268 | 4. Add the UI config schema to `BEHAVIOR_SCHEMAS` in `internal/api/ui/index.html` |
| 269 | 5. Write tests: bot logic, config parsing, edge cases. IRC connection can be skipped in unit tests. |
| 270 | 6. Update this bootstrap |
| 271 | |
| 272 | No separate registration file or global registry. The manager builds bots by ID from the `BotSpec`. Bots satisfy the `bot` interface (unexported in manager package): |
| 273 | |
| 274 | ```go |
| 275 | type bot interface { |
| @@ -315,14 +349,28 @@ | |
| 315 | go build ./cmd/scuttlectl # build CLI |
| 316 | go test ./... # run all tests |
| 317 | golangci-lint run # lint |
| 318 | |
| 319 | # Admin CLI |
| 320 | scuttlectl admin list # list admin accounts |
| 321 | scuttlectl admin add alice # add admin (prompts for password) |
| 322 | scuttlectl admin passwd alice # change password |
| 323 | scuttlectl admin remove alice # remove admin |
| 324 | |
| 325 | # Docker |
| 326 | docker compose -f deploy/compose/docker-compose.yml up |
| 327 | ``` |
| 328 | |
| 329 |
| --- bootstrap.md | |
| +++ bootstrap.md | |
| @@ -157,24 +157,26 @@ | |
| 157 | - `+v` (voice) — trusted worker agents |
| 158 | - no mode — standard agents |
| 159 | |
| 160 | ### Built-in bots |
| 161 | |
| 162 | All 10 bots are implemented. Enabled/configured via the web UI or `scuttlectl bot list`. The manager (`internal/bots/manager/`) starts/stops them dynamically when policies change. All bots set `+B` (bot) user mode on connect and auto-accept INVITE. |
| 163 | |
| 164 | | Bot | Nick | Role | |
| 165 | |-----|------|------| |
| 166 | | `auditbot` | auditbot | Immutable append-only audit trail of agent actions and credential events | |
| 167 | | `herald` | herald | Routes inbound webhook events to IRC channels | |
| 168 | | `oracle` | oracle | On-demand channel summarization via DM — calls any OpenAI-compatible LLM | |
| 169 | | `scribe` | scribe | Structured message logging to rotating files (jsonl/csv/text) | |
| 170 | | `scroll` | scroll | History replay to PM on request (`replay #channel [format=toon]`) | |
| 171 | | `sentinel` | sentinel | LLM-powered channel observer — detects policy violations, posts structured incident reports to mod channel. Never takes enforcement action. | |
| 172 | | `snitch` | snitch | Flood and join/part cycling detection, MONITOR-based presence tracking, away-notify alerts | |
| 173 | | `steward` | steward | Acts on sentinel incident reports — issues warnings, mutes (extended ban `m:`), or kicks based on severity | |
| 174 | | `systembot` | systembot | Logs IRC system events (joins, parts, quits, mode changes) | |
| 175 | | `warden` | warden | Channel moderation — warn → mute (extended ban) → kick on flood | |
| 176 | |
| 177 | Oracle uses TOON format (`pkg/toon/`) for token-efficient LLM context. Scroll supports `format=toon` for compact replay output. Configure `api_key_env` to the name of the env var holding the API key (e.g. `ORACLE_OPENAI_API_KEY`), and `base_url` for non-OpenAI providers. |
| 178 | |
| 179 | ### Scale |
| 180 | |
| 181 | Target: 100s to low 1000s of agents on a private network. Single Ergo instance handles this comfortably (documented up to 10k clients, 2k per channel). Ergo scales up (multi-core), not out — no horizontal clustering today. Federation is planned upstream but has no timeline; not a scuttlebot concern for now. |
| 182 | |
| @@ -186,11 +188,12 @@ | |
| 188 | |------|------|-------| |
| 189 | | Agent registry | `data/ergo/registry.json` | Agent records + SASL credentials | |
| 190 | | Admin accounts | `data/ergo/admins.json` | bcrypt-hashed; created by `scuttlectl admin add` | |
| 191 | | Policies | `data/ergo/policies.json` | Bot config, agent policy, logging settings | |
| 192 | | Bot passwords | `data/ergo/bot_passwords.json` | Auto-generated SASL passwords for system bots | |
| 193 | | API token | `data/ergo/api_token` | Legacy token; migrated to api_keys.json on first run | |
| 194 | | API keys | `data/ergo/api_keys.json` | Per-consumer tokens with scoped permissions (SHA-256 hashed) | |
| 195 | | Ergo state | `data/ergo/ircd.db` | Ergo-native: accounts, channels, topics, history | |
| 196 | | scribe logs | `data/logs/scribe/` | Rotating log files (jsonl/csv/text); configurable | |
| 197 | |
| 198 | K8s / Docker: mount a PersistentVolume at `data/`. Ergo is single-instance — HA = fast pod restart with durable storage, not horizontal scaling. |
| 199 | |
| @@ -228,48 +231,79 @@ | |
| 231 | `internal/api/` — two-mux pattern: |
| 232 | |
| 233 | - **Outer mux** (unauthenticated): `POST /login`, `GET /` (redirect), `GET /ui/` (web UI) |
| 234 | - **Inner mux** (`/v1/` routes): require `Authorization: Bearer <token>` header |
| 235 | |
| 236 | ### Auth |
| 237 | |
| 238 | API keys are per-consumer tokens with scoped permissions. Each key has a name, scopes, optional expiry, and last-used tracking. Scopes: `admin`, `agents`, `channels`, `chat`, `topology`, `bots`, `config`, `read`. The `admin` scope implies all others. |
| 239 | |
| 240 | `POST /login` accepts `{username, password}` and returns a 24h session token with admin scope. Rate limited to 10 attempts per minute per IP. |
| 241 | |
| 242 | On first run, the legacy `api_token` file is migrated into `api_keys.json` as the first admin-scope key. New keys are created via `POST /v1/api-keys`, `scuttlectl api-key create`, or the web UI settings tab. |
| 243 | |
| 244 | Admin accounts managed via `scuttlectl admin` or web UI. First run auto-creates `admin` with a random password printed to the log. |
| 245 | |
| 246 | ### Key endpoints |
| 247 | |
| 248 | All `/v1/` endpoints require a Bearer token with the appropriate scope. |
| 249 | |
| 250 | | Method | Path | Scope | Description | |
| 251 | |--------|------|-------|-------------| |
| 252 | | `POST` | `/login` | — | Username/password login (unauthenticated) | |
| 253 | | `GET` | `/v1/status` | read | Server status | |
| 254 | | `GET` | `/v1/metrics` | read | Runtime metrics + bridge stats | |
| 255 | | `GET` | `/v1/settings` | read | Full settings (policies, TLS, bot commands) | |
| 256 | | `GET/PUT/PATCH` | `/v1/settings/policies` | admin | Bot config, agent policy, logging | |
| 257 | | `GET` | `/v1/agents` | agents | List all registered agents | |
| 258 | | `GET` | `/v1/agents/{nick}` | agents | Get single agent | |
| 259 | | `PATCH` | `/v1/agents/{nick}` | agents | Update agent | |
| 260 | | `POST` | `/v1/agents/register` | agents | Register an agent | |
| 261 | | `POST` | `/v1/agents/{nick}/rotate` | agents | Rotate credentials | |
| 262 | | `POST` | `/v1/agents/{nick}/adopt` | agents | Adopt existing IRC nick | |
| 263 | | `POST` | `/v1/agents/{nick}/revoke` | agents | Revoke agent credentials | |
| 264 | | `DELETE` | `/v1/agents/{nick}` | agents | Delete agent | |
| 265 | | `GET` | `/v1/channels` | channels | List joined channels | |
| 266 | | `POST` | `/v1/channels/{ch}/join` | channels | Join channel | |
| 267 | | `DELETE` | `/v1/channels/{ch}` | channels | Leave channel | |
| 268 | | `GET` | `/v1/channels/{ch}/messages` | channels | Get message history | |
| 269 | | `POST` | `/v1/channels/{ch}/messages` | chat | Send message | |
| 270 | | `POST` | `/v1/channels/{ch}/presence` | chat | Touch presence (keep web user visible) | |
| 271 | | `GET` | `/v1/channels/{ch}/users` | channels | User list with IRC modes | |
| 272 | | `GET` | `/v1/channels/{ch}/config` | channels | Per-channel display config | |
| 273 | | `PUT` | `/v1/channels/{ch}/config` | channels | Set display config (mirror detail, render mode) | |
| 274 | | `GET` | `/v1/channels/{ch}/stream` | channels | SSE stream (`?token=` query param auth) | |
| 275 | | `POST` | `/v1/channels` | topology | Provision channel via ChanServ | |
| 276 | | `DELETE` | `/v1/topology/channels/{ch}` | topology | Drop channel | |
| 277 | | `GET` | `/v1/topology` | topology | Channel types, static channels, active channels | |
| 278 | | `GET/PUT` | `/v1/config` | config | Server config read/write | |
| 279 | | `GET` | `/v1/config/history` | config | Config change history | |
| 280 | | `GET/POST` | `/v1/admins` | admin | List / add admin accounts | |
| 281 | | `DELETE` | `/v1/admins/{username}` | admin | Remove admin | |
| 282 | | `PUT` | `/v1/admins/{username}/password` | admin | Change password | |
| 283 | | `GET/POST` | `/v1/api-keys` | admin | List / create API keys | |
| 284 | | `DELETE` | `/v1/api-keys/{id}` | admin | Revoke API key | |
| 285 | | `GET/POST/PUT/DELETE` | `/v1/llm/backends[/{name}]` | bots | LLM backend CRUD | |
| 286 | | `GET` | `/v1/llm/backends/{name}/models` | bots | List models for backend | |
| 287 | | `POST` | `/v1/llm/discover` | bots | Discover models from provider | |
| 288 | | `POST` | `/v1/llm/complete` | bots | LLM completion proxy | |
| 289 | |
| 290 | --- |
| 291 | |
| 292 | ## Adding a New Bot |
| 293 | |
| 294 | 1. Create `internal/bots/{name}/` package with a `Bot` struct and `Start(ctx context.Context) error` method |
| 295 | 2. Set `+B` user mode on connect, handle INVITE for auto-join |
| 296 | 3. Add a `BotSpec` config struct if the bot needs user-configurable settings |
| 297 | 4. Register in `internal/bots/manager/manager.go`: |
| 298 | - Add a case to `buildBot()` that constructs your bot from the spec config |
| 299 | - Add a `BehaviorConfig` entry to `defaultBehaviors` in `internal/api/policies.go` |
| 300 | 5. Add commands to `botCommands` map in `internal/api/policies.go` for the web UI command reference |
| 301 | 6. Add the UI config schema to `BEHAVIOR_SCHEMAS` in `internal/api/ui/index.html` |
| 302 | 7. Use `internal/bots/cmdparse/` for command routing if the bot accepts DM commands |
| 303 | 8. Write tests: bot logic, config parsing, edge cases. IRC connection can be skipped in unit tests. |
| 304 | 9. Update this bootstrap |
| 305 | |
| 306 | No separate registration file or global registry. The manager builds bots by ID from the `BotSpec`. Bots satisfy the `bot` interface (unexported in manager package): |
| 307 | |
| 308 | ```go |
| 309 | type bot interface { |
| @@ -315,14 +349,28 @@ | |
| 349 | go build ./cmd/scuttlectl # build CLI |
| 350 | go test ./... # run all tests |
| 351 | golangci-lint run # lint |
| 352 | |
| 353 | # Admin CLI |
| 354 | scuttlectl status # server health |
| 355 | scuttlectl admin list # list admin accounts |
| 356 | scuttlectl admin add alice # add admin (prompts for password) |
| 357 | scuttlectl admin passwd alice # change password |
| 358 | scuttlectl admin remove alice # remove admin |
| 359 | scuttlectl api-key list # list API keys |
| 360 | scuttlectl api-key create --name "relay" --scopes chat,channels |
| 361 | scuttlectl api-key revoke <id> # revoke key |
| 362 | scuttlectl topology list # show channel types + static channels |
| 363 | scuttlectl topology provision #channel # create channel |
| 364 | scuttlectl topology drop #channel # remove channel |
| 365 | scuttlectl config show # dump config JSON |
| 366 | scuttlectl config history # config change history |
| 367 | scuttlectl bot list # show system bot status |
| 368 | scuttlectl agent list # list agents |
| 369 | scuttlectl agent register <nick> --type worker --channels #fleet |
| 370 | scuttlectl agent rotate <nick> # rotate credentials |
| 371 | scuttlectl backend list # LLM backends |
| 372 | |
| 373 | # Docker |
| 374 | docker compose -f deploy/compose/docker-compose.yml up |
| 375 | ``` |
| 376 | |
| 377 |