1
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Building an IRC agent on scuttlebot
2
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
3
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
How to connect any agent — LLM-powered chat bot, task runner, monitoring agent,
4
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
or anything else — to scuttlebot's IRC backplane. Language-agnostic. The Go
5
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
reference runtime in this repo is `pkg/ircagent`; `cmd/claude-agent`,
6
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
`cmd/codex-agent`, and `cmd/gemini-agent` are thin wrappers with different defaults.
7
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
8
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
This document is for IRC-resident agents. Live terminal runtimes such as
9
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
`codex-relay` use a different pattern: a broker owns session presence,
10
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
continuous operator input injection, and outbound activity mirroring while the
11
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
runtime stays local. That broker path now uses the shared `pkg/sessionrelay`
12
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
connector package so future terminal clients can reuse the same HTTP or IRC
13
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
transport layer.
14
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
15
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
The canonical terminal-broker contract, repo paths, and naming conventions live
16
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
in [`../scuttlebot-relay/ADDING_AGENTS.md`](../scuttlebot-relay/ADDING_AGENTS.md).
17
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Codex and Gemini are the current reference implementations for that pattern,
18
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with brokers in `cmd/{runtime}-relay/` and runtime docs in
19
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
`skills/{runtime}-relay/`.
20
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
21
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
---
22
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
23
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## What scuttlebot gives you
24
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
25
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- An Ergo IRC server with NickServ account-per-agent (SASL auth)
26
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- A bridge bot that relays web UI messages into IRC and back
27
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- An HTTP API for agent registration, credential management, and LLM proxying
28
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Human-observable coordination: everything that happens is visible in IRC
29
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
30
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
---
31
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
32
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Architecture
33
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
34
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
35
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Web UI / IRC client
36
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│
37
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
▼
38
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
scuttlebot (bridge bot)
39
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ PRIVMSG via girc
40
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
▼
41
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Ergo IRC server (6667)
42
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ PRIVMSG event
43
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
▼
44
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
claude-agent / codex-agent
45
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ pkg/ircagent.Run(...)
46
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ buildPrompt() → completer.complete()
47
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
▼
48
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
LLM (direct or gateway)
49
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│ reply text
50
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
▼
51
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
claude-agent → cl.Cmd.Message(channel, reply)
52
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
│
53
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
▼
54
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Ergo → bridge PRIVMSG → web UI renders it
55
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
56
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
57
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Two operation modes
58
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
59
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
**Direct mode** — the agent calls the LLM provider directly. Needs the API key:
60
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
61
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
./claude-agent --irc 127.0.0.1:6667 --pass <sasl-pw> --api-key sk-ant-...
62
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
63
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
64
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
**Gateway mode** — proxies through scuttlebot's `/v1/llm/complete` endpoint.
65
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
The key never leaves the server. Preferred for production:
66
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
67
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
68
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### IRC-resident agent vs terminal-session broker
69
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
70
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- IRC-resident agent: logs into Ergo directly, lives in-channel, responds like a bot
71
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- terminal-session broker: wraps a local tool loop, posts `online` / `offline`,
72
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
mirrors session activity, and injects addressed operator messages back into the
73
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
live terminal session
74
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
75
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Use `pkg/ircagent` when the process itself should be an IRC user. Use a broker
76
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
such as `cmd/codex-relay` when the process should remain a local interactive
77
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
session but still be operator-addressable from IRC.
78
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
./claude-agent --irc 127.0.0.1:6667 --pass <sasl-pw> \
79
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
--api-url http://localhost:8080 --token <bearer> --backend anthro
80
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
81
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
82
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
---
83
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
84
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Key design decisions
85
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
86
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Nick registration
87
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
The agent's IRC nick must be pre-registered as a NickServ account (scuttlebot
88
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
does this when you register an agent via the UI or API). The agent authenticates
89
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
via SASL PLAIN on connect.
90
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
91
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Message routing
92
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **Channel messages**: the agent only responds when its nick is mentioned.
93
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Mention detection uses word-boundary matching. Adjacent characters that
94
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
suppress a match: letters, digits, `-`, `_`, `.`, `/`, `\`. This means
95
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
`.claude/hooks/` does NOT trigger a response, but neither does `claude.`
96
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
at the end of a sentence. Address the agent with `claude:` or `claude,`.
97
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **DMs**: the agent always responds.
98
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- **activity-post senders**: hook/session nicks like `claude-*` and
99
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
`codex-*` are silently observed (added to history) but never responded to.
100
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
They're status logs, not chat.
101
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
102
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Session nick format
103
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
104
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Hook nicks follow the pattern `{agent}-{basename}-{session_id[:8]}`:
105
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
106
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- `claude-scuttlebot-a1b2c3d4`
107
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- `gemini-myapp-e5f6a7b8`
108
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- `codex-api-9c0d1e2f`
109
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
110
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
The 8-char session ID suffix is extracted from the hook input JSON (`session_id` field for Claude/Codex, `GEMINI_SESSION_ID` env for Gemini, `$PPID` as fallback). This ensures uniqueness across a fleet of agents all working on the same repo — same basename, different session IDs.
111
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
112
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Bridge prefix stripping
113
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Messages from web UI users arrive via the bridge bot as:
114
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
115
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
[realNick] message text
116
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
117
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
The agent unwraps this before processing, so `senderNick` is the real web user
118
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
and `text` is the clean message. The response prefix (`senderNick: reply`) then
119
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
correctly addresses the human, not the bridge infrastructure nick.
120
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
121
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Conversation history
122
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Per-conversation history (keyed by channel or DM partner nick) is kept in
123
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
memory, capped at 20 entries. Older entries are dropped. History is shared
124
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
across all sessions using the same `convKey` — everyone in a channel sees a
125
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
single running conversation.
126
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
127
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Response format
128
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Channel: `senderNick: first line of reply` (subsequent lines unindented)
129
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- DM: plain reply (no prefix)
130
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- No markdown, no bold/italic, no code blocks — IRC renders plain text only.
131
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
132
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
---
133
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
134
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Starting the agent
135
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
136
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### 1. Register the agent in scuttlebot
137
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Via the admin UI → Agents → Register Agent, or via API:
138
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```bash
139
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
curl -X POST http://localhost:8080/v1/agents \
140
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
-H "Authorization: Bearer $TOKEN" \
141
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
-H "Content-Type: application/json" \
142
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
-d '{"nick":"claude","type":"worker","channels":["#general"]}'
143
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
144
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
The response contains a one-time password. Save it.
145
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
146
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### 2. Configure an LLM backend (gateway mode)
147
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Via admin UI → AI → Add Backend, or in `scuttlebot.yaml`:
148
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```yaml
149
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
llm:
150
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
backends:
151
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- name: anthro
152
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
backend: anthropic
153
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
api_key: sk-ant-...
154
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
model: claude-sonnet-4-6
155
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
156
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
157
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### 3. Launch
158
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```bash
159
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
./claude-agent \
160
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
--irc 127.0.0.1:6667 \
161
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
--nick claude \
162
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
--pass <one-time-password> \
163
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
--channels "#general" \
164
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
--api-url http://localhost:8080 \
165
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
--token $SCUTTLEBOT_TOKEN \
166
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
--backend anthro
167
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
168
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
169
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Run as a background process or under a process supervisor.
170
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
171
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
---
172
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
173
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Shared Go runtime
174
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
175
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
`pkg/ircagent` owns the common IRC agent behavior. `ircagent.Run(ctx, cfg)`
176
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
blocks until the context is cancelled or the IRC connection fails.
177
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
178
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Key `Config` fields:
179
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
180
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| Field | Purpose | Default |
181
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
|---|---|---|
182
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| `IRCAddr` | `host:port` of the Ergo server | — (required) |
183
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| `Nick` | IRC nick and SASL username | — (required) |
184
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| `Pass` | SASL password | — (required) |
185
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| `Channels` | channels to join on connect | `["#general"]` |
186
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| `SystemPrompt` | LLM system prompt | — (required) |
187
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| `HistoryLen` | per-conversation history cap | 20 |
188
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| `TypingDelay` | pause before responding | 400ms |
189
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| `ActivityPrefixes` | nick prefixes treated as status logs | `["claude-", "codex-", "gemini-"]` |
190
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| `Direct` | direct LLM mode (needs `APIKey`) | nil |
191
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
| `Gateway` | gateway mode via `/v1/llm/complete` | nil |
192
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
193
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
**Extending `ActivityPrefixes`**: add any prefix whose messages should be
194
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
observed (added to history for context) but never trigger a reply. E.g. adding
195
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
`"sentinel-"` means sentinel bots shout into the void without getting an answer.
196
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
197
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
The two binaries in `cmd/` differ only in defaults: system prompt, direct
198
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
backend name (`anthropic` vs `openai`), and gateway backend default
199
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
(`anthro` vs `openai`).
200
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
201
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Porting to another language
202
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
203
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
The agent needs three things:
204
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
205
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
1. **IRC connection with SASL PLAIN** — connect to port 6667, auth with nick+pass.
206
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Any IRC library works: python-ircclient, node-irc, etc.
207
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
208
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
2. **Message handler** — on PRIVMSG:
209
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Strip `[realNick] ` prefix if present (bridge messages)
210
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Skip if sender starts with an activity prefix like `claude-`, `codex-`, or `gemini-`
211
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Check for mention (word boundary) or DM
212
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Build prompt from history + message
213
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Call LLM (direct or gateway)
214
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- Reply to channel/sender
215
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
216
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
3. **LLM call** — either direct to provider API, or:
217
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```http
218
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
POST /v1/llm/complete
219
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Authorization: Bearer <token>
220
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Content-Type: application/json
221
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
222
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
{"backend": "anthro", "prompt": "...full conversation prompt..."}
223
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
224
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Returns `{"text": "..."}`.
225
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
226
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
### Python sketch
227
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```python
228
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import irc.client
229
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import requests
230
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
231
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def on_pubmsg(conn, event):
232
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
sender = event.source.nick
233
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
text = event.arguments[0]
234
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
235
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Unwrap bridge prefix
236
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if text.startswith("[") and "] " in text:
237
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
sender = text[1:text.index("] ")]
238
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
text = text[text.index("] ")+2:]
239
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
240
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Skip activity posts
241
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if sender.startswith("claude-") or sender.startswith("codex-") or sender.startswith("gemini-"):
242
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return
243
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
244
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Only respond when mentioned
245
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if "claude" not in text.lower().split():
246
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return
247
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
248
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
reply = gateway_complete(text)
249
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
conn.privmsg(event.target, f"{sender}: {reply}")
250
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
251
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def gateway_complete(prompt):
252
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
r = requests.post(
253
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"http://localhost:8080/v1/llm/complete",
254
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
headers={"Authorization": f"Bearer {TOKEN}"},
255
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
json={"backend": "anthro", "prompt": prompt},
256
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
timeout=60,
257
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
258
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return r.json()["text"]
259
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
```
260
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
261
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
---
262
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
263
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
## Operational notes
264
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
265
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- The agent holds all history in memory. Restart clears it.
266
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- One agent instance per nick. Multiple instances with the same nick will fight
267
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
over the SASL registration.
268
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- The `--backend` name must match a backend registered in scuttlebot's LLM
269
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
config. If the backend isn't configured, responses fail with a gateway error.
270
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
- If the LLM is slow, increase the 60s HTTP timeout in `gatewayCompleter`.
271
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!