AtlatestRepositorycourier
1
# Courier Relays: Agent Communication via Local Sockets3
## Overview5
This document describes an enhancement to Courier that adds **relay**6
support — local Unix socket channels that enable real-time bidirectional7
communication between a leader Claude session and worker agent sessions.9
Relays extend Courier's existing `send-message` tool with a new transport.10
Instead of building a separate tool for agent coordination, Courier gains the11
ability to route messages to local agents the same way it routes messages to12
humans via Telegram. The leader doesn't think about transports — it just sends13
a message to a recipient.15
Messages on relays are ephemeral — short, human-readable text meant to signal16
state changes and prompt action. Durable context (task details, progress logs,17
design decisions) belongs in folio notes, not relay messages.19
## Problem21
When a leader session spawns worker agents (via tmux), there's no reactive22
communication channel between them. The leader has to poll files to check on23
progress. Workers have no way to signal the leader when they're blocked or24
done. This gap was identified when the Ops session had to be manually prompted25
to check on worker state.27
## Design29
### Concepts31
**Relay**: A named Unix socket that connects exactly two Courier instances —32
a listener and a connector. Messages pass through in real-time with no33
buffering or persistence.35
**Leader**: The Courier instance that creates and listens on relays. This36
is typically the Ops session or any session that spawns workers.38
**Worker**: A Courier instance launched with `--relay <name>`, which39
connects to an existing relay socket. The worker sees the relay as a40
message recipient.42
### Message Flow44
```45
Leader Session Worker Session46
┌──────────────┐ ┌──────────────┐47
│ Claude │ │ Claude │48
│ │ │ │49
│ send-message│ │ send-message│50
│ to: "agent" │ │ to: "leader"│51
│ │ │ │52
│ channel │ │ channel │53
│ notify! ◄───┼────────────────────────┼──── notify! │54
└──────┬───────┘ └──────┬───────┘55
│ stdio │ stdio56
┌──────┴───────┐ Unix socket ┌──────┴───────┐57
│ Courier │◄══════════════════════►│ Courier │58
│ (leader) │ ~/.courier/relays/ │ (worker) │59
│ │ agent-name.sock │ │60
│ Telegram ◄──┤ │ no Telegram │61
│ Relays │ │ 1 relay │62
└──────────────┘ └──────────────┘63
```65
### Unified send-message67
The existing `send-message` tool gains relay awareness. Courier maintains68
a recipient registry:70
- **Telegram recipients**: identified by chat_id (existing behavior)71
- **Relay recipients**: identified by relay name73
The tool's `to:` parameter accepts either:74
```75
send-message to: "331005009" text: "..." → Telegram (chat ID)76
send-message to: "sigil-agent" text: "..." → relay socket77
send-message to: "leader" text: "..." → relay (worker mode)78
```80
When a message arrives on a relay (from either direction), it's delivered81
as a channel notification to the Claude session, just like Telegram messages:83
```xml84
<channel source="courier" sender="sigil-agent" type="relay">85
Done with step 2, see folio notes for details86
</channel>87
```89
### Leader Usage91
The leader's Courier exposes a `create-relay` tool:93
```94
create-relay name: "sigil-agent"95
```97
This creates a Unix socket at `~/.courier/relays/sigil-agent.sock` and98
starts listening for a connection. The relay name becomes a valid recipient99
for `send-message`.101
The leader can create multiple relays — one per worker agent it spawns.103
A `list-relays` tool shows active relays and connection status:105
```106
list-relays107
→ sigil-agent: connected (uptime 2h)108
sc-agent: waiting for connection109
```111
A `close-relay` tool tears down a relay when the worker is done:113
```114
close-relay name: "sigil-agent"115
```117
### Worker Usage119
A worker's Courier is launched with:121
```122
courier --relay sigil-agent123
```125
This connects to `~/.courier/relays/sigil-agent.sock`. The worker's126
Courier:128
- Registers "leader" as a recipient (the other end of the relay)129
- Does NOT start Telegram polling (no token needed)130
- Exposes the same `send-message` tool, but with "leader" as the only131
non-local recipient133
The worker sends messages to the leader:135
```136
send-message to: "leader" text: "Blocked on test failures, need guidance"137
```139
The leader receives this as a channel notification immediately.141
### Socket Protocol143
Messages on the socket are newline-delimited JSON:145
```json146
{"sender": "sigil-agent", "text": "Step 2 complete, see folio"}147
{"sender": "leader", "text": "Move on to step 3"}148
```150
Simple, no framing complexity. Each side reads lines and delivers them as151
channel notifications to their Claude session.153
### Socket Path Convention155
All relay sockets live in `~/.courier/relays/`. The name maps directly156
to the filename:158
```159
~/.courier/relays/sigil-agent.sock160
~/.courier/relays/sc-agent.sock161
```163
Workers only need the relay name, not the full path.165
## Updated Courier Instructions167
The instructions string sent to Claude in the MCP handshake is updated168
based on mode:170
**Leader mode** (Telegram + relays):171
```172
Messages from Telegram arrive as channel notifications with type="telegram".173
Messages from worker agents arrive with type="relay" and the agent's name174
as the sender.176
Use send-message with a chat_id to reply via Telegram, or with a relay177
name to message a worker agent.179
Use create-relay to set up a communication channel before spawning a180
worker agent. Use list-relays to see active connections.181
```183
**Worker mode** (relay only):184
```185
You are a worker agent connected to a leader session via relay.186
Messages from the leader arrive as channel notifications with type="relay".188
Use send-message to: "leader" to communicate back to the leader session.189
Keep messages short — use folio notes for detailed context.190
```192
## Configuration194
### Leader (existing courier.yaml, extended)196
```yaml197
allowed-senders:198
- "331005009"200
relays:201
socket-dir: ~/.courier/relays202
```204
### Worker (CLI args only, no config file needed)206
```207
courier --relay sigil-agent --log logs/courier.log --log-level trace208
```210
No `.env-courier` needed for workers (no Telegram token). The relay name211
is all the config a worker needs.213
## Spawning Workers215
The leader session handles spawning — it knows the project context, worktree216
needs, and MCP config. A typical flow:218
1. Leader calls `create-relay name: "sigil-agent"`219
2. Leader creates a tmux session with Claude, passing MCP config inline:220
```bash221
tmux new-session -d -s sigil-agent \222
claude --mcp-config '{"mcpServers":{"courier":{"command":"courier","args":["--relay","sigil-agent","--log","logs/courier.log"]}}}' \223
--project-dir ~/Projects/Code/sigil/sigil \224
...225
```226
Or by writing a `.mcp.json` in the project directory.227
3. Worker starts, courier connects to the relay socket228
4. Leader sees "sigil-agent: connected" in `list-relays`229
5. Leader sends initial task: `send-message to: "sigil-agent" text: "..."`231
The spawning logic can be systematized as a shared skill installed across232
repos — a skill that knows how to set up tmux sessions, configure MCP,233
and start Claude with the right flags and permissions.235
## Resilience237
### Worker crash/disconnect239
When a worker's courier disconnects (process dies, tmux session killed):240
- Leader's courier detects the closed socket241
- Delivers a channel notification:242
```xml243
<channel source="courier" sender="sigil-agent" type="relay-disconnect">244
Agent disconnected245
</channel>246
```247
- Leader can decide whether to respawn or clean up249
### Leader restart251
If the leader's courier restarts:252
- Relay sockets are recreated by the leader253
- Workers using `--relay` retry connection with backoff254
- Short disruption, but recoverable256
### System restart258
Relay sockets are ephemeral (Unix sockets disappear on reboot). After a259
restart:260
- Leader recreates relays as needed261
- Workers are re-spawned (using folio task notes with in-progress status262
to determine what needs to resume)263
- Workers reconnect via `--relay` and continue264
- `claude --continue` resumes the previous conversation in the worker's265
project directory267
## Implementation Plan269
### Phase 1: Socket Infrastructure271
- [ ] Unix socket creation/listening in courier272
- [ ] Socket connection (client mode) via `--relay` CLI arg273
- [ ] Newline-delimited JSON message protocol over socket274
- [ ] Deliver incoming socket messages as channel notifications275
- [ ] `~/.courier/relays/` directory convention277
### Phase 2: Leader Tools279
- [ ] `create-relay` tool: create socket, start listening280
- [ ] `close-relay` tool: tear down socket, notify if connected281
- [ ] `list-relays` tool: show active relays and connection status282
- [ ] Update `send-message` to route by recipient type (chat_id vs relay name)283
- [ ] Disconnect detection and notification285
### Phase 3: Worker Mode287
- [ ] `--relay <name>` CLI flag: connect to existing socket288
- [ ] Register "leader" as default recipient289
- [ ] Skip Telegram setup when in worker mode290
- [ ] Updated instructions string for worker context291
- [ ] Connection retry with backoff293
### Phase 4: Resilience295
- [ ] Reconnection on socket drop (worker side)296
- [ ] Relay recreation on leader restart297
- [ ] Integration with folio task notes for resumption after system restart299
## Dependencies301
No new dependencies. Courier already has everything it needs:302
- **sigil-async**: goroutines for socket I/O alongside stdin polling303
- **sigil-json**: message serialization304
- **sigil-log**: structured logging306
Unix socket support may need a small addition to sigil-stdlib or sigil-lib307
if not already exposed.309
## What This Replaces311
This design replaces the earlier Foreman proposal. The key insight: Foreman312
kept narrowing down to "just message passing between leader and workers" —313
which is exactly what Courier already does, just with a different transport.315
Agent orchestration (deciding what to spawn, managing worktrees, tracking316
tasks) stays in the leader session's instructions and skills. Courier317
provides the communication fabric. Folio provides shared knowledge.319
## Open Questions321
1. **Multiple relay connections**: Should a relay support more than two322
endpoints? Current design is 1:1 (leader <> worker). Broadcast would323
require a different topology.325
2. **Shared skill for spawning**: The pattern of "create relay, set up326
tmux, write MCP config, start Claude" could be a shared skill installed327
across repos. Design TBD.329
3. **Unix socket API in Sigil**: Need to verify that sigil-lib exposes330
Unix domain socket creation/connection, or add it.