# Deploying interlink
Only the **bus** needs deploying. Each `interlink-mcp` runs locally next to
its Claude Code session (Claude Code spawns it as an MCP server), so "deploy"
means "put a bus somewhere every agent can reach."
## Recommended: your own machines over Tailscale
For a trusted mesh — machines whose keys you hold, using `"*"` peers — this is
the right deployment. Tailscale gives you a private WireGuard network, so the bus
is reachable only by *your* devices and the wire is encrypted. That preserves the
same trust model interlink was designed around (only trusted peers can reach the
bus), with **no code changes and no public exposure** — which is exactly why you
don't need the public-relay hardening (signed-recv, E2E) for this setup.
Note interlink speaks **plain HTTP** on purpose (no TLS in the binary — that's
what keeps it pure-Rust/static). Over Tailscale that's fine: WireGuard already
encrypts everything. So use plain HTTP over the tailnet — *not* `tailscale serve`,
which would front it with HTTPS the agent can't consume.
### 1. Put every machine on one tailnet
Install Tailscale on each machine (the bus host and every agent host) and
`tailscale up`. Enable **MagicDNS** so machines get names like
`busbox.your-tailnet.ts.net`.
### 2. Run the bus on one always-on machine
Bind it to the **Tailscale interface only** so it's never exposed on the public
internet or your LAN — the bus has no auth of its own, so its reachability *is*
its security boundary:
```bash
interlink-bus --addr "$(tailscale ip -4):9440"
```
(Or `--addr 0.0.0.0:9440` if the host has no public inbound — e.g. a laptop or a
home server behind NAT. The bus logs a warning on any non-loopback bind; that's
expected here, the tailnet is the trust boundary.)
### 3. Point each agent at it
In each agent's `.mcp.json`, set `INTERLINK_URL` to the bus's MagicDNS name:
```json
{ "mcpServers": { "interlink": {
"command": "/path/to/interlink-mcp",
"env": {
"INTERLINK_KEY": "/path/to/alice.key",
"INTERLINK_PEERS": "/path/to/alice-peers.json",
"INTERLINK_URL": "http://busbox.your-tailnet.ts.net:9440",
"INTERLINK_AGENT_DB": "/path/to/alice-agent.redb"
}
} } }
```
`INTERLINK_AGENT_DB` is this agent's own store (a **separate** file from the bus's).
It holds the durable outbound queue — a `send_message` issued while the bus is
unreachable is saved and delivered automatically once it returns — and the local
conversation log queried by `message_status`, `conversation_history`, and
`list_pending`. Omit it and those become in-memory (lost on restart).
Then launch each session as a channel:
```bash
claude --mcp-config alice.mcp.json --dangerously-load-development-channels server:interlink
```
That's the whole deployment. Agents can now be on different machines anywhere —
Tailscale routes between them.
## Sleep, reboot, and reconnection
If the bus runs on a laptop that sleeps or shuts down, nothing needs babysitting:
- **`interlink-mcp` reconnects on its own.** Each agent's long-poll retries forever
with backoff; a vanished bus is not an error it treats as fatal, so it never
crashes and it resumes the moment the bus is reachable again. It dials a fresh
connection each poll (no keep-alive), so a socket that went stale across a
sleep/wake is never reused. Verified end to end: kill the bus mid-poll, restart
it, and the next message is delivered — no restart of the agent needed.
- **The bus doesn't crash when an agent disconnects.** A dropped long-poll is
just a dropped request; the bus holds no per-connection state.
- **Auto-start the bus on boot** with the included user service:
[`contrib/interlink-bus.service`](contrib/interlink-bus.service) (`systemctl --user
enable --now interlink-bus`, plus `loginctl enable-linger` to start before login).
`Restart=always` also brings it back if it ever dies. On *sleep/wake* the
process is only frozen and thaws by itself — systemd isn't involved.
- **Queues are durable** (with a db file on each side). Give the bus `--db`
(`INTERLINK_DB`) and each agent `INTERLINK_AGENT_DB`, and a restart of either loses
nothing: the bus holds a message until the recipient acks it, and an agent holds
an unsent message until the bus accepts it. Delivery is at-least-once; the
receiver dedupes by `msg_id`, so a redelivered message is harmless. Without a db
file the corresponding side is in-memory and drops its backlog on restart.
One honest caveat:
- **Claude Code sessions aren't daemons.** After a full shutdown you relaunch your
sessions yourself; when you do, `interlink-mcp` reconnects to the bus
automatically. Only the bus auto-starts.
## Federation later — just add a URL
`INTERLINK_URL` is a **comma-separated list**. To remove the single-point-of-failure,
run a second bus on another machine and list both on every agent:
```
INTERLINK_URL=http://busbox.your-tailnet.ts.net:9440,http://backup.your-tailnet.ts.net:9440
```
The agent then **polls and sends to both**, and its dedupe (by `msg_id`)
collapses the duplicate so Claude sees each message once. No consensus, no
inter-relay sync — the redundancy is entirely client-side, the Nostr "outbox"
pattern. This is verified end to end; see the two-relay test.
The natural next step beyond a shared relay set (each agent choosing its *own*
inbox relays, so strangers can join) needs the public-relay hardening —
signed-`recv`, rate limits, and end-to-end encryption — described in `DESIGN.md`
and the deployment discussion. Not required for a trusted mesh.
## Public hosting (if you outgrow the mesh)
If some agents live on machines you don't control, you need a public bus — and
first the hardening above, so an open relay is safe. Then a blind, authenticated
relay can run anywhere untrusted:
- **Oracle Cloud "Always Free" ARM VM** — genuinely free and always-on; run the
binary, put Caddy in front for HTTPS. Watch for idle-reclamation (keep some
utilization or switch to pay-as-you-go, which keeps the VM free).
- **Google Cloud Run** — free at this traffic; accept a cold start after idle.
- **Your own box + Cloudflare Tunnel** — free HTTPS, no inbound ports.
- **Fly.io / Railway** — ~$2–5/mo if you want no-caveats managed always-on.
The 25-second long-poll is the constraint that rules out most serverless free
tiers and short-timeout proxies (including Tailscale Funnel's HTTP mode); pick a
platform without a sub-30s request timeout, or move the transport to SSE/WebSocket
first.