|
6d0d615…
|
lmata
|
1 |
# scuttlebot Bootstrap |
|
6d0d615…
|
lmata
|
2 |
|
|
6d0d615…
|
lmata
|
3 |
This is the primary conventions document. All agent shims (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `calliope.md`) point here. |
|
6d0d615…
|
lmata
|
4 |
|
|
6d0d615…
|
lmata
|
5 |
An agent given this document and a business requirement should be able to generate correct, idiomatic code without exploring the codebase. |
|
6d0d615…
|
lmata
|
6 |
|
|
6d0d615…
|
lmata
|
7 |
--- |
|
6d0d615…
|
lmata
|
8 |
|
|
1ab586b…
|
lmata
|
9 |
## Why IRC (and not NATS or RabbitMQ) |
|
1ab586b…
|
lmata
|
10 |
|
|
1ab586b…
|
lmata
|
11 |
The short answer: IRC is a coordination protocol. NATS and RabbitMQ are message brokers. The difference matters. |
|
1ab586b…
|
lmata
|
12 |
|
|
1ab586b…
|
lmata
|
13 |
### IRC |
|
1ab586b…
|
lmata
|
14 |
|
|
1ab586b…
|
lmata
|
15 |
IRC has presence, identity, channels, topics, ops hierarchy, DMs, and bots — natively. These map directly to agent coordination concepts without bolting anything on. A channel is a team. A topic is shared state. Ops are authority. Bots are services. It all just works. |
|
1ab586b…
|
lmata
|
16 |
|
|
1ab586b…
|
lmata
|
17 |
It is also **human observable by default**. No dashboards, no special tooling, no translation layer. Open any IRC client, join a channel, and you see exactly what agents are doing. This is the single biggest advantage for debugging and operating agent systems. |
|
1ab586b…
|
lmata
|
18 |
|
|
1ab586b…
|
lmata
|
19 |
Other properties that matter for agent coordination: |
|
1ab586b…
|
lmata
|
20 |
- **Latency tolerant** — fire-and-forget, designed for unreliable networks. Agents can reconnect, miss messages, catch up via history. This is a feature, not a limitation. |
|
1ab586b…
|
lmata
|
21 |
- **Battle-tested** — 35+ years, RFC 1459 (1993), proven at scale. Not going anywhere. |
|
1ab586b…
|
lmata
|
22 |
- **Self-hostable, zero vendor lock-in** — Ergo is MIT, single Go binary. No cloud, no subscription. |
|
1ab586b…
|
lmata
|
23 |
- **Bots are a solved problem** — NickServ, ChanServ, BotServ, 35 years of tooling. We inherit all of it. |
|
1ab586b…
|
lmata
|
24 |
- **Simple enough to debug naked** — the protocol is plain text. When something breaks, you can read it. |
|
1ab586b…
|
lmata
|
25 |
|
|
1ab586b…
|
lmata
|
26 |
### Why not NATS |
|
1ab586b…
|
lmata
|
27 |
|
|
1ab586b…
|
lmata
|
28 |
NATS is excellent and fast. It is the right choice when you need guaranteed delivery, high-throughput pub/sub, or JetStream persistence at scale. It is not the right choice here because: |
|
1ab586b…
|
lmata
|
29 |
|
|
1ab586b…
|
lmata
|
30 |
- No native presence model — you cannot `WHOIS` a subject or see who is subscribed to a stream |
|
1ab586b…
|
lmata
|
31 |
- No ops hierarchy — authority and trust are not protocol concepts |
|
1ab586b…
|
lmata
|
32 |
- Not human observable without NATS-specific tooling (no standard client exists for "just watching") |
|
1ab586b…
|
lmata
|
33 |
- More moving pieces — JetStream, clustering, leaf nodes, consumers, streams. Powerful but not simple. |
|
1ab586b…
|
lmata
|
34 |
- The subject hierarchy (`project.myapp.tasks`) is conceptually identical to our channel naming convention — if we ever needed to swap, the mapping is straightforward |
|
1ab586b…
|
lmata
|
35 |
|
|
1ab586b…
|
lmata
|
36 |
### Why not RabbitMQ |
|
1ab586b…
|
lmata
|
37 |
|
|
1ab586b…
|
lmata
|
38 |
RabbitMQ is a serious enterprise message broker designed for guaranteed delivery workflows. It is operationally heavy (Erlang runtime, clustering, exchanges, bindings, queues), not human observable without a management UI, and not designed for real-time coordination between actors. Wrong tool for this job. |
|
1ab586b…
|
lmata
|
39 |
|
|
1ab586b…
|
lmata
|
40 |
### Swappability |
|
1ab586b…
|
lmata
|
41 |
|
|
1ab586b…
|
lmata
|
42 |
The JSON envelope format and the SDK abstraction (`pkg/client/`) are intentionally transport-agnostic. The channel naming convention maps cleanly to NATS subjects. If a use case demands NATS-level throughput or delivery guarantees, swapping the transport is a backend concern that does not affect the agent-facing API. |
|
1ab586b…
|
lmata
|
43 |
|
|
1ab586b…
|
lmata
|
44 |
--- |
|
1ab586b…
|
lmata
|
45 |
|
|
6d0d615…
|
lmata
|
46 |
## What is scuttlebot |
|
6d0d615…
|
lmata
|
47 |
|
|
6d0d615…
|
lmata
|
48 |
An agent coordination backplane built on IRC. Agents connect as IRC users, coordinate via channels, and communicate via structured messages. IRC is an implementation detail — users configure scuttlebot, never Ergo directly. |
|
6d0d615…
|
lmata
|
49 |
|
|
6d0d615…
|
lmata
|
50 |
**Why IRC:** lightweight TCP transport, encryption, channels, presence, ops hierarchy, DMs, human observable by default. Humans and agents share the same backplane with no translation layer. |
|
6d0d615…
|
lmata
|
51 |
|
|
6d0d615…
|
lmata
|
52 |
**Ergo** (https://ergo.chat) is the IRC server. scuttlebot manages its lifecycle and config. Federation, auth, history, TLS, rate limiting — all Ergo. scuttlebot abstracts it. |
|
6d0d615…
|
lmata
|
53 |
|
|
6d0d615…
|
lmata
|
54 |
--- |
|
6d0d615…
|
lmata
|
55 |
|
|
6d0d615…
|
lmata
|
56 |
## Monorepo Layout |
|
6d0d615…
|
lmata
|
57 |
|
|
6d0d615…
|
lmata
|
58 |
``` |
|
6d0d615…
|
lmata
|
59 |
cmd/ |
|
5ac549c…
|
lmata
|
60 |
scuttlebot/ # daemon binary |
|
5ac549c…
|
lmata
|
61 |
scuttlectl/ # admin CLI |
|
5ac549c…
|
lmata
|
62 |
internal/apiclient/ # typed API client used by scuttlectl |
|
6d0d615…
|
lmata
|
63 |
internal/ |
|
5ac549c…
|
lmata
|
64 |
api/ # HTTP API server (Bearer auth) + embedded web UI at /ui/ |
|
5ac549c…
|
lmata
|
65 |
ui/index.html # single-file operator web UI |
|
5ac549c…
|
lmata
|
66 |
auth/ # admin account store — bcrypt hashed, persisted to JSON |
|
5ac549c…
|
lmata
|
67 |
bots/ |
|
5ac549c…
|
lmata
|
68 |
manager/ # bot lifecycle — starts/stops bots on policy change |
|
5ac549c…
|
lmata
|
69 |
auditbot/ # immutable append-only audit trail |
|
5ac549c…
|
lmata
|
70 |
herald/ # external event → channel routing (webhooks) |
|
5ac549c…
|
lmata
|
71 |
oracle/ # on-demand channel summarization via LLM (PM only) |
|
5ac549c…
|
lmata
|
72 |
scribe/ # structured logging to rotating files |
|
5ac549c…
|
lmata
|
73 |
scroll/ # history replay to PM on request |
|
5ac549c…
|
lmata
|
74 |
snitch/ # flood + join/part cycling detection → operator alerts |
|
5ac549c…
|
lmata
|
75 |
systembot/ # IRC system events (joins, parts, modes, kicks) |
|
5ac549c…
|
lmata
|
76 |
warden/ # channel moderation — warn → mute → kick |
|
5ac549c…
|
lmata
|
77 |
config/ # YAML config loading + validation |
|
5ac549c…
|
lmata
|
78 |
ergo/ # Ergo IRC server lifecycle + config generation |
|
5ac549c…
|
lmata
|
79 |
mcp/ # MCP server for AI agent connectivity |
|
5ac549c…
|
lmata
|
80 |
registry/ # agent registration + SASL credential issuance |
|
5ac549c…
|
lmata
|
81 |
topology/ # channel provisioning + mode/topic management |
|
6d0d615…
|
lmata
|
82 |
pkg/ |
|
5ac549c…
|
lmata
|
83 |
client/ # Go agent SDK (public) |
|
5ac549c…
|
lmata
|
84 |
protocol/ # JSON envelope wire format |
|
6d0d615…
|
lmata
|
85 |
deploy/ |
|
5ac549c…
|
lmata
|
86 |
docker/ # Dockerfile(s) |
|
5ac549c…
|
lmata
|
87 |
compose/ # Docker Compose (local dev + single-host) |
|
5ac549c…
|
lmata
|
88 |
k8s/ # Kubernetes manifests |
|
5ac549c…
|
lmata
|
89 |
standalone/ # single binary, no container required |
|
5ac549c…
|
lmata
|
90 |
tests/ |
|
5ac549c…
|
lmata
|
91 |
e2e/ # Playwright end-to-end tests (require scuttlebot running) |
|
6d0d615…
|
lmata
|
92 |
go.mod |
|
6d0d615…
|
lmata
|
93 |
go.sum |
|
5ac549c…
|
lmata
|
94 |
bootstrap.md |
|
5ac549c…
|
lmata
|
95 |
CLAUDE.md # Claude Code shim — points here |
|
6d0d615…
|
lmata
|
96 |
``` |
|
6d0d615…
|
lmata
|
97 |
|
|
5ac549c…
|
lmata
|
98 |
Single Go module. All state persisted as JSON files under `data/` (no database required). |
|
6d0d615…
|
lmata
|
99 |
|
|
6d0d615…
|
lmata
|
100 |
--- |
|
6d0d615…
|
lmata
|
101 |
|
|
6d0d615…
|
lmata
|
102 |
## Architecture |
|
6d0d615…
|
lmata
|
103 |
|
|
6d0d615…
|
lmata
|
104 |
### Ergo relationship |
|
6d0d615…
|
lmata
|
105 |
|
|
6d0d615…
|
lmata
|
106 |
scuttlebot owns the Ergo process and config. Users never edit `ircd.yaml` directly. scuttlebot generates it from its own config and manages Ergo as a subprocess. |
|
6d0d615…
|
lmata
|
107 |
|
|
6d0d615…
|
lmata
|
108 |
- Ergo provides: TLS, SASL accounts, channel persistence, message history, ops hierarchy, server federation, rate limiting |
|
6d0d615…
|
lmata
|
109 |
- scuttlebot provides: agent registration, topology provisioning, rules-of-engagement delivery, built-in bots, SDK/MCP layer |
|
6d0d615…
|
lmata
|
110 |
|
|
6d0d615…
|
lmata
|
111 |
### Agent lifecycle |
|
6d0d615…
|
lmata
|
112 |
|
|
6d0d615…
|
lmata
|
113 |
1. Agent calls scuttlebot registration endpoint |
|
6d0d615…
|
lmata
|
114 |
2. scuttlebot creates Ergo account, issues SASL credentials |
|
6d0d615…
|
lmata
|
115 |
3. On connect, agent receives signed rules-of-engagement payload (channel assignments, engagement rules, permissions) |
|
6d0d615…
|
lmata
|
116 |
4. Agent connects to Ergo with SASL credentials |
|
6d0d615…
|
lmata
|
117 |
5. scuttlebot verifies presence, assigns channel modes |
|
6d0d615…
|
lmata
|
118 |
|
|
6d0d615…
|
lmata
|
119 |
### Channel topology |
|
6d0d615…
|
lmata
|
120 |
|
|
6d0d615…
|
lmata
|
121 |
Hierarchical, configurable. Convention: |
|
6d0d615…
|
lmata
|
122 |
|
|
6d0d615…
|
lmata
|
123 |
``` |
|
6d0d615…
|
lmata
|
124 |
#fleet fleet-wide, quiet, announcements only |
|
6d0d615…
|
lmata
|
125 |
#project.{name} project coordination |
|
6d0d615…
|
lmata
|
126 |
#project.{name}.{topic} swarming, chatty, active work |
|
6d0d615…
|
lmata
|
127 |
#project.{name}.{topic}.{subtopic} deep nesting |
|
6d0d615…
|
lmata
|
128 |
#task.{id} ephemeral, auto-created/destroyed |
|
6d0d615…
|
lmata
|
129 |
#agent.{name} agent-specific inbox |
|
6d0d615…
|
lmata
|
130 |
``` |
|
6d0d615…
|
lmata
|
131 |
|
|
6d0d615…
|
lmata
|
132 |
Users define topology in scuttlebot config. scuttlebot provisions the channels, sets modes and topics. |
|
6d0d615…
|
lmata
|
133 |
|
|
6d0d615…
|
lmata
|
134 |
### Wire format |
|
6d0d615…
|
lmata
|
135 |
|
|
6d0d615…
|
lmata
|
136 |
- **Agent messages:** JSON envelope in `PRIVMSG` |
|
6d0d615…
|
lmata
|
137 |
- **System/status:** `NOTICE` — human readable, machines ignore |
|
6d0d615…
|
lmata
|
138 |
- **Agent context packets** (summarization, history replay): TOON format (token-efficient for LLM consumption) |
|
6d0d615…
|
lmata
|
139 |
|
|
6d0d615…
|
lmata
|
140 |
JSON envelope structure: |
|
6d0d615…
|
lmata
|
141 |
|
|
6d0d615…
|
lmata
|
142 |
```json |
|
6d0d615…
|
lmata
|
143 |
{ |
|
6d0d615…
|
lmata
|
144 |
"v": 1, |
|
6d0d615…
|
lmata
|
145 |
"type": "task.create", |
|
6d0d615…
|
lmata
|
146 |
"id": "ulid", |
|
6d0d615…
|
lmata
|
147 |
"from": "agent-nick", |
|
6d0d615…
|
lmata
|
148 |
"ts": 1234567890, |
|
6d0d615…
|
lmata
|
149 |
"payload": {} |
|
6d0d615…
|
lmata
|
150 |
} |
|
6d0d615…
|
lmata
|
151 |
``` |
|
6d0d615…
|
lmata
|
152 |
|
|
6d0d615…
|
lmata
|
153 |
### Authority / trust hierarchy |
|
6d0d615…
|
lmata
|
154 |
|
|
6d0d615…
|
lmata
|
155 |
IRC ops model maps directly: |
|
6d0d615…
|
lmata
|
156 |
- `+o` (channel op) — orchestrator agents, privileged |
|
6d0d615…
|
lmata
|
157 |
- `+v` (voice) — trusted worker agents |
|
6d0d615…
|
lmata
|
158 |
- no mode — standard agents |
|
6d0d615…
|
lmata
|
159 |
|
|
6d0d615…
|
lmata
|
160 |
### Built-in bots |
|
6d0d615…
|
lmata
|
161 |
|
|
a1cd907…
|
noreply
|
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. |
|
5ac549c…
|
lmata
|
163 |
|
|
5ac549c…
|
lmata
|
164 |
| Bot | Nick | Role | |
|
5ac549c…
|
lmata
|
165 |
|-----|------|------| |
|
5ac549c…
|
lmata
|
166 |
| `auditbot` | auditbot | Immutable append-only audit trail of agent actions and credential events | |
|
5ac549c…
|
lmata
|
167 |
| `herald` | herald | Routes inbound webhook events to IRC channels | |
|
5ac549c…
|
lmata
|
168 |
| `oracle` | oracle | On-demand channel summarization via DM — calls any OpenAI-compatible LLM | |
|
5ac549c…
|
lmata
|
169 |
| `scribe` | scribe | Structured message logging to rotating files (jsonl/csv/text) | |
|
a1cd907…
|
noreply
|
170 |
| `scroll` | scroll | History replay to PM on request (`replay #channel [format=toon]`) | |
|
a1cd907…
|
noreply
|
171 |
| `sentinel` | sentinel | LLM-powered channel observer — detects policy violations, posts structured incident reports to mod channel. Never takes enforcement action. | |
|
a1cd907…
|
noreply
|
172 |
| `snitch` | snitch | Flood and join/part cycling detection, MONITOR-based presence tracking, away-notify alerts | |
|
a1cd907…
|
noreply
|
173 |
| `steward` | steward | Acts on sentinel incident reports — issues warnings, mutes (extended ban `m:`), or kicks based on severity | |
|
5ac549c…
|
lmata
|
174 |
| `systembot` | systembot | Logs IRC system events (joins, parts, quits, mode changes) | |
|
a1cd907…
|
noreply
|
175 |
| `warden` | warden | Channel moderation — warn → mute (extended ban) → kick on flood | |
|
5ac549c…
|
lmata
|
176 |
|
|
a1cd907…
|
noreply
|
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. |
|
6d0d615…
|
lmata
|
178 |
|
|
6d0d615…
|
lmata
|
179 |
### Scale |
|
6d0d615…
|
lmata
|
180 |
|
|
6d0d615…
|
lmata
|
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. |
|
6d0d615…
|
lmata
|
182 |
|
|
6d0d615…
|
lmata
|
183 |
### Persistence |
|
6d0d615…
|
lmata
|
184 |
|
|
5ac549c…
|
lmata
|
185 |
No database required. All state is persisted as JSON files under `data/` by default. |
|
5ac549c…
|
lmata
|
186 |
|
|
5ac549c…
|
lmata
|
187 |
| What | File | Notes | |
|
5ac549c…
|
lmata
|
188 |
|------|------|-------| |
|
5ac549c…
|
lmata
|
189 |
| Agent registry | `data/ergo/registry.json` | Agent records + SASL credentials | |
|
5ac549c…
|
lmata
|
190 |
| Admin accounts | `data/ergo/admins.json` | bcrypt-hashed; created by `scuttlectl admin add` | |
|
5ac549c…
|
lmata
|
191 |
| Policies | `data/ergo/policies.json` | Bot config, agent policy, logging settings | |
|
5ac549c…
|
lmata
|
192 |
| Bot passwords | `data/ergo/bot_passwords.json` | Auto-generated SASL passwords for system bots | |
|
a1cd907…
|
noreply
|
193 |
| API token | `data/ergo/api_token` | Legacy token; migrated to api_keys.json on first run | |
|
a1cd907…
|
noreply
|
194 |
| API keys | `data/ergo/api_keys.json` | Per-consumer tokens with scoped permissions (SHA-256 hashed) | |
|
5ac549c…
|
lmata
|
195 |
| Ergo state | `data/ergo/ircd.db` | Ergo-native: accounts, channels, topics, history | |
|
5ac549c…
|
lmata
|
196 |
| scribe logs | `data/logs/scribe/` | Rotating log files (jsonl/csv/text); configurable | |
|
5ac549c…
|
lmata
|
197 |
|
|
5ac549c…
|
lmata
|
198 |
K8s / Docker: mount a PersistentVolume at `data/`. Ergo is single-instance — HA = fast pod restart with durable storage, not horizontal scaling. |
|
6d0d615…
|
lmata
|
199 |
|
|
6d0d615…
|
lmata
|
200 |
--- |
|
6d0d615…
|
lmata
|
201 |
|
|
6d0d615…
|
lmata
|
202 |
## Conventions |
|
6d0d615…
|
lmata
|
203 |
|
|
6d0d615…
|
lmata
|
204 |
### Go |
|
6d0d615…
|
lmata
|
205 |
|
|
6d0d615…
|
lmata
|
206 |
- Go 1.22+ |
|
6d0d615…
|
lmata
|
207 |
- `gofmt` + `golangci-lint` |
|
6d0d615…
|
lmata
|
208 |
- Errors returned, not panicked. Wrap with context: `fmt.Errorf("registry: create account: %w", err)` |
|
6d0d615…
|
lmata
|
209 |
- Interfaces defined at point of use, not in the package that implements them |
|
6d0d615…
|
lmata
|
210 |
- No global state. Dependencies injected via struct fields or constructor args. |
|
6d0d615…
|
lmata
|
211 |
- Config via struct + YAML/TOML — no env var spaghetti (env vars for secrets only) |
|
6d0d615…
|
lmata
|
212 |
|
|
6d0d615…
|
lmata
|
213 |
### Tests |
|
6d0d615…
|
lmata
|
214 |
|
|
6d0d615…
|
lmata
|
215 |
- `go test ./...` |
|
6d0d615…
|
lmata
|
216 |
- Integration tests use a real Ergo instance (Docker Compose in CI) |
|
6d0d615…
|
lmata
|
217 |
- Assert against observable state — channel membership, messages received, account existence |
|
6d0d615…
|
lmata
|
218 |
- Both happy path and error cases |
|
6d0d615…
|
lmata
|
219 |
- No mocking the IRC connection in integration tests |
|
6d0d615…
|
lmata
|
220 |
|
|
6d0d615…
|
lmata
|
221 |
### Commits + branches |
|
6d0d615…
|
lmata
|
222 |
|
|
6d0d615…
|
lmata
|
223 |
- Branch: `feature/{issue}-short-description` or `fix/{issue}-short-description` |
|
6d0d615…
|
lmata
|
224 |
- No rebases. New commits only. |
|
6d0d615…
|
lmata
|
225 |
- No AI attribution in commits. |
|
6d0d615…
|
lmata
|
226 |
|
|
6d0d615…
|
lmata
|
227 |
--- |
|
6d0d615…
|
lmata
|
228 |
|
|
5ac549c…
|
lmata
|
229 |
## HTTP API |
|
5ac549c…
|
lmata
|
230 |
|
|
5ac549c…
|
lmata
|
231 |
`internal/api/` — two-mux pattern: |
|
5ac549c…
|
lmata
|
232 |
|
|
5ac549c…
|
lmata
|
233 |
- **Outer mux** (unauthenticated): `POST /login`, `GET /` (redirect), `GET /ui/` (web UI) |
|
5ac549c…
|
lmata
|
234 |
- **Inner mux** (`/v1/` routes): require `Authorization: Bearer <token>` header |
|
5ac549c…
|
lmata
|
235 |
|
|
5ac549c…
|
lmata
|
236 |
### Auth |
|
5ac549c…
|
lmata
|
237 |
|
|
a1cd907…
|
noreply
|
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. |
|
a1cd907…
|
noreply
|
239 |
|
|
a1cd907…
|
noreply
|
240 |
`POST /login` accepts `{username, password}` and returns a 24h session token with admin scope. Rate limited to 10 attempts per minute per IP. |
|
a1cd907…
|
noreply
|
241 |
|
|
a1cd907…
|
noreply
|
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. |
|
5ac549c…
|
lmata
|
243 |
|
|
a1cd907…
|
noreply
|
244 |
Admin accounts managed via `scuttlectl admin` or web UI. First run auto-creates `admin` with a random password printed to the log. |
|
5ac549c…
|
lmata
|
245 |
|
|
5ac549c…
|
lmata
|
246 |
### Key endpoints |
|
5ac549c…
|
lmata
|
247 |
|
|
a1cd907…
|
noreply
|
248 |
All `/v1/` endpoints require a Bearer token with the appropriate scope. |
|
a1cd907…
|
noreply
|
249 |
|
|
a1cd907…
|
noreply
|
250 |
| Method | Path | Scope | Description | |
|
a1cd907…
|
noreply
|
251 |
|--------|------|-------|-------------| |
|
a1cd907…
|
noreply
|
252 |
| `POST` | `/login` | — | Username/password login (unauthenticated) | |
|
a1cd907…
|
noreply
|
253 |
| `GET` | `/v1/status` | read | Server status | |
|
a1cd907…
|
noreply
|
254 |
| `GET` | `/v1/metrics` | read | Runtime metrics + bridge stats | |
|
a1cd907…
|
noreply
|
255 |
| `GET` | `/v1/settings` | read | Full settings (policies, TLS, bot commands) | |
|
a1cd907…
|
noreply
|
256 |
| `GET/PUT/PATCH` | `/v1/settings/policies` | admin | Bot config, agent policy, logging | |
|
a1cd907…
|
noreply
|
257 |
| `GET` | `/v1/agents` | agents | List all registered agents | |
|
a1cd907…
|
noreply
|
258 |
| `GET` | `/v1/agents/{nick}` | agents | Get single agent | |
|
a1cd907…
|
noreply
|
259 |
| `PATCH` | `/v1/agents/{nick}` | agents | Update agent | |
|
a1cd907…
|
noreply
|
260 |
| `POST` | `/v1/agents/register` | agents | Register an agent | |
|
a1cd907…
|
noreply
|
261 |
| `POST` | `/v1/agents/{nick}/rotate` | agents | Rotate credentials | |
|
a1cd907…
|
noreply
|
262 |
| `POST` | `/v1/agents/{nick}/adopt` | agents | Adopt existing IRC nick | |
|
a1cd907…
|
noreply
|
263 |
| `POST` | `/v1/agents/{nick}/revoke` | agents | Revoke agent credentials | |
|
a1cd907…
|
noreply
|
264 |
| `DELETE` | `/v1/agents/{nick}` | agents | Delete agent | |
|
a1cd907…
|
noreply
|
265 |
| `GET` | `/v1/channels` | channels | List joined channels | |
|
a1cd907…
|
noreply
|
266 |
| `POST` | `/v1/channels/{ch}/join` | channels | Join channel | |
|
a1cd907…
|
noreply
|
267 |
| `DELETE` | `/v1/channels/{ch}` | channels | Leave channel | |
|
a1cd907…
|
noreply
|
268 |
| `GET` | `/v1/channels/{ch}/messages` | channels | Get message history | |
|
a1cd907…
|
noreply
|
269 |
| `POST` | `/v1/channels/{ch}/messages` | chat | Send message | |
|
a1cd907…
|
noreply
|
270 |
| `POST` | `/v1/channels/{ch}/presence` | chat | Touch presence (keep web user visible) | |
|
a1cd907…
|
noreply
|
271 |
| `GET` | `/v1/channels/{ch}/users` | channels | User list with IRC modes | |
|
a1cd907…
|
noreply
|
272 |
| `GET` | `/v1/channels/{ch}/config` | channels | Per-channel display config | |
|
a1cd907…
|
noreply
|
273 |
| `PUT` | `/v1/channels/{ch}/config` | channels | Set display config (mirror detail, render mode) | |
|
a1cd907…
|
noreply
|
274 |
| `GET` | `/v1/channels/{ch}/stream` | channels | SSE stream (`?token=` query param auth) | |
|
a1cd907…
|
noreply
|
275 |
| `POST` | `/v1/channels` | topology | Provision channel via ChanServ | |
|
a1cd907…
|
noreply
|
276 |
| `DELETE` | `/v1/topology/channels/{ch}` | topology | Drop channel | |
|
a1cd907…
|
noreply
|
277 |
| `GET` | `/v1/topology` | topology | Channel types, static channels, active channels | |
|
a1cd907…
|
noreply
|
278 |
| `GET/PUT` | `/v1/config` | config | Server config read/write | |
|
a1cd907…
|
noreply
|
279 |
| `GET` | `/v1/config/history` | config | Config change history | |
|
a1cd907…
|
noreply
|
280 |
| `GET/POST` | `/v1/admins` | admin | List / add admin accounts | |
|
a1cd907…
|
noreply
|
281 |
| `DELETE` | `/v1/admins/{username}` | admin | Remove admin | |
|
a1cd907…
|
noreply
|
282 |
| `PUT` | `/v1/admins/{username}/password` | admin | Change password | |
|
a1cd907…
|
noreply
|
283 |
| `GET/POST` | `/v1/api-keys` | admin | List / create API keys | |
|
a1cd907…
|
noreply
|
284 |
| `DELETE` | `/v1/api-keys/{id}` | admin | Revoke API key | |
|
a1cd907…
|
noreply
|
285 |
| `GET/POST/PUT/DELETE` | `/v1/llm/backends[/{name}]` | bots | LLM backend CRUD | |
|
a1cd907…
|
noreply
|
286 |
| `GET` | `/v1/llm/backends/{name}/models` | bots | List models for backend | |
|
a1cd907…
|
noreply
|
287 |
| `POST` | `/v1/llm/discover` | bots | Discover models from provider | |
|
a1cd907…
|
noreply
|
288 |
| `POST` | `/v1/llm/complete` | bots | LLM completion proxy | |
|
5ac549c…
|
lmata
|
289 |
|
|
5ac549c…
|
lmata
|
290 |
--- |
|
5ac549c…
|
lmata
|
291 |
|
|
6d0d615…
|
lmata
|
292 |
## Adding a New Bot |
|
6d0d615…
|
lmata
|
293 |
|
|
5ac549c…
|
lmata
|
294 |
1. Create `internal/bots/{name}/` package with a `Bot` struct and `Start(ctx context.Context) error` method |
|
a1cd907…
|
noreply
|
295 |
2. Set `+B` user mode on connect, handle INVITE for auto-join |
|
a1cd907…
|
noreply
|
296 |
3. Add a `BotSpec` config struct if the bot needs user-configurable settings |
|
a1cd907…
|
noreply
|
297 |
4. Register in `internal/bots/manager/manager.go`: |
|
5ac549c…
|
lmata
|
298 |
- Add a case to `buildBot()` that constructs your bot from the spec config |
|
5ac549c…
|
lmata
|
299 |
- Add a `BehaviorConfig` entry to `defaultBehaviors` in `internal/api/policies.go` |
|
a1cd907…
|
noreply
|
300 |
5. Add commands to `botCommands` map in `internal/api/policies.go` for the web UI command reference |
|
a1cd907…
|
noreply
|
301 |
6. Add the UI config schema to `BEHAVIOR_SCHEMAS` in `internal/api/ui/index.html` |
|
a1cd907…
|
noreply
|
302 |
7. Use `internal/bots/cmdparse/` for command routing if the bot accepts DM commands |
|
a1cd907…
|
noreply
|
303 |
8. Write tests: bot logic, config parsing, edge cases. IRC connection can be skipped in unit tests. |
|
a1cd907…
|
noreply
|
304 |
9. Update this bootstrap |
|
5ac549c…
|
lmata
|
305 |
|
|
5ac549c…
|
lmata
|
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): |
|
5ac549c…
|
lmata
|
307 |
|
|
5ac549c…
|
lmata
|
308 |
```go |
|
5ac549c…
|
lmata
|
309 |
type bot interface { |
|
5ac549c…
|
lmata
|
310 |
Start(ctx context.Context) error |
|
5ac549c…
|
lmata
|
311 |
} |
|
5ac549c…
|
lmata
|
312 |
``` |
|
6d0d615…
|
lmata
|
313 |
|
|
6d0d615…
|
lmata
|
314 |
--- |
|
6d0d615…
|
lmata
|
315 |
|
|
6d0d615…
|
lmata
|
316 |
## Adding a New SDK |
|
6d0d615…
|
lmata
|
317 |
|
|
6d0d615…
|
lmata
|
318 |
1. Create `sdk/{language}/` as its own module |
|
6d0d615…
|
lmata
|
319 |
2. Implement the client interface defined in `pkg/client/` as reference |
|
6d0d615…
|
lmata
|
320 |
3. Cover: connect, register, send message, receive message, disconnect |
|
6d0d615…
|
lmata
|
321 |
4. Own CI workflow in `.github/workflows/sdk-{language}.yml` |
|
6d0d615…
|
lmata
|
322 |
|
|
6d0d615…
|
lmata
|
323 |
--- |
|
6d0d615…
|
lmata
|
324 |
|
|
6d0d615…
|
lmata
|
325 |
## Ports (local) |
|
6d0d615…
|
lmata
|
326 |
|
|
6d0d615…
|
lmata
|
327 |
| Service | Address | |
|
6d0d615…
|
lmata
|
328 |
|---------|---------| |
|
6d0d615…
|
lmata
|
329 |
| Ergo IRC | `ircs://localhost:6697` | |
|
6d0d615…
|
lmata
|
330 |
| scuttlebot API | `http://localhost:8080` | |
|
6d0d615…
|
lmata
|
331 |
| MCP server | `http://localhost:8081` | |
|
6d0d615…
|
lmata
|
332 |
|
|
6d0d615…
|
lmata
|
333 |
--- |
|
6d0d615…
|
lmata
|
334 |
|
|
6d0d615…
|
lmata
|
335 |
## Common Commands |
|
6d0d615…
|
lmata
|
336 |
|
|
6d0d615…
|
lmata
|
337 |
```bash |
|
5ac549c…
|
lmata
|
338 |
# Dev helper (recommended) |
|
5ac549c…
|
lmata
|
339 |
./run.sh # build + start |
|
5ac549c…
|
lmata
|
340 |
./run.sh restart # rebuild + restart |
|
5ac549c…
|
lmata
|
341 |
./run.sh stop # stop |
|
5ac549c…
|
lmata
|
342 |
./run.sh token # print current API token |
|
5ac549c…
|
lmata
|
343 |
./run.sh log # tail the log |
|
5ac549c…
|
lmata
|
344 |
./run.sh test # go test ./... |
|
5ac549c…
|
lmata
|
345 |
./run.sh e2e # Playwright e2e (requires scuttlebot running) |
|
5ac549c…
|
lmata
|
346 |
|
|
5ac549c…
|
lmata
|
347 |
# Direct Go commands |
|
6d0d615…
|
lmata
|
348 |
go build ./cmd/scuttlebot # build daemon |
|
6d0d615…
|
lmata
|
349 |
go build ./cmd/scuttlectl # build CLI |
|
6d0d615…
|
lmata
|
350 |
go test ./... # run all tests |
|
6d0d615…
|
lmata
|
351 |
golangci-lint run # lint |
|
5ac549c…
|
lmata
|
352 |
|
|
5ac549c…
|
lmata
|
353 |
# Admin CLI |
|
a1cd907…
|
noreply
|
354 |
scuttlectl status # server health |
|
5ac549c…
|
lmata
|
355 |
scuttlectl admin list # list admin accounts |
|
5ac549c…
|
lmata
|
356 |
scuttlectl admin add alice # add admin (prompts for password) |
|
5ac549c…
|
lmata
|
357 |
scuttlectl admin passwd alice # change password |
|
5ac549c…
|
lmata
|
358 |
scuttlectl admin remove alice # remove admin |
|
a1cd907…
|
noreply
|
359 |
scuttlectl api-key list # list API keys |
|
a1cd907…
|
noreply
|
360 |
scuttlectl api-key create --name "relay" --scopes chat,channels |
|
a1cd907…
|
noreply
|
361 |
scuttlectl api-key revoke <id> # revoke key |
|
a1cd907…
|
noreply
|
362 |
scuttlectl topology list # show channel types + static channels |
|
a1cd907…
|
noreply
|
363 |
scuttlectl topology provision #channel # create channel |
|
a1cd907…
|
noreply
|
364 |
scuttlectl topology drop #channel # remove channel |
|
a1cd907…
|
noreply
|
365 |
scuttlectl config show # dump config JSON |
|
a1cd907…
|
noreply
|
366 |
scuttlectl config history # config change history |
|
a1cd907…
|
noreply
|
367 |
scuttlectl bot list # show system bot status |
|
a1cd907…
|
noreply
|
368 |
scuttlectl agent list # list agents |
|
a1cd907…
|
noreply
|
369 |
scuttlectl agent register <nick> --type worker --channels #fleet |
|
a1cd907…
|
noreply
|
370 |
scuttlectl agent rotate <nick> # rotate credentials |
|
a1cd907…
|
noreply
|
371 |
scuttlectl backend list # LLM backends |
|
5ac549c…
|
lmata
|
372 |
|
|
5ac549c…
|
lmata
|
373 |
# Docker |
|
5ac549c…
|
lmata
|
374 |
docker compose -f deploy/compose/docker-compose.yml up |
|
5ac549c…
|
lmata
|
375 |
``` |
|
10b6d92…
|
lmata
|
376 |
|
|
10b6d92…
|
lmata
|
377 |
## Optional: IRC Chatbot Agents |
|
10b6d92…
|
lmata
|
378 |
|
|
10b6d92…
|
lmata
|
379 |
`cmd/claude-agent`, `cmd/codex-agent`, and `cmd/gemini-agent` are standalone IRC bots that connect to a channel and respond to prompts using an LLM backend. They are **not part of the default build** — they exist as a reference pattern for operators who want a persistent chatbot presence in a channel. |
|
10b6d92…
|
lmata
|
380 |
|
|
10b6d92…
|
lmata
|
381 |
These are distinct from the relay brokers (`claude-relay`, `codex-relay`, `gemini-relay`). The difference: |
|
10b6d92…
|
lmata
|
382 |
|
|
10b6d92…
|
lmata
|
383 |
| | Chatbot agent | Relay broker | |
|
10b6d92…
|
lmata
|
384 |
|---|---|---| |
|
10b6d92…
|
lmata
|
385 |
| Wraps a coding CLI | No | Yes | |
|
10b6d92…
|
lmata
|
386 |
| Reads/writes files, runs commands | No | Yes (via the CLI) | |
|
10b6d92…
|
lmata
|
387 |
| Always-on, responds to any mention | Yes | No — tied to an active session | |
|
10b6d92…
|
lmata
|
388 |
| Useful for fleet coordination | Novelty only | Core pattern | |
|
10b6d92…
|
lmata
|
389 |
|
|
10b6d92…
|
lmata
|
390 |
The relay broker is the right tool for agent work. The chatbot agent is a nice-to-have for operators who want an LLM available in IRC for quick Q&A, but it cannot act — it can only respond. |
|
10b6d92…
|
lmata
|
391 |
|
|
10b6d92…
|
lmata
|
392 |
### Running one |
|
10b6d92…
|
lmata
|
393 |
|
|
10b6d92…
|
lmata
|
394 |
```bash |
|
10b6d92…
|
lmata
|
395 |
# Build (not included in make all) |
|
10b6d92…
|
lmata
|
396 |
make chatbots |
|
10b6d92…
|
lmata
|
397 |
|
|
10b6d92…
|
lmata
|
398 |
# Register a nick in scuttlebot |
|
10b6d92…
|
lmata
|
399 |
TOKEN=$(./run.sh token) |
|
10b6d92…
|
lmata
|
400 |
curl -s -X POST -H "Authorization: Bearer $TOKEN" \ |
|
10b6d92…
|
lmata
|
401 |
-H "Content-Type: application/json" \ |
|
10b6d92…
|
lmata
|
402 |
-d '{"nick":"claude","type":"worker","channels":["#general"]}' \ |
|
10b6d92…
|
lmata
|
403 |
http://localhost:8080/v1/agents/register |
|
10b6d92…
|
lmata
|
404 |
|
|
10b6d92…
|
lmata
|
405 |
# Connect (use the passphrase from the register response) |
|
10b6d92…
|
lmata
|
406 |
bin/claude-agent --irc 127.0.0.1:6667 --nick claude --pass <passphrase> \ |
|
10b6d92…
|
lmata
|
407 |
--api-url http://localhost:8080 --token $TOKEN --backend anthro |
|
10b6d92…
|
lmata
|
408 |
``` |
|
10b6d92…
|
lmata
|
409 |
|
|
10b6d92…
|
lmata
|
410 |
Swap `claude-agent` → `codex-agent` (backend `openai`) or `gemini-agent` (backend `gemini`) for other providers. All three accept the same flags. |