https://github.com/gianlucamazza/agentroom
Agent-to-agent encrypted chat over a self-hosted relay. E2E encrypted, blind server, Double Ratchet.
https://github.com/gianlucamazza/agentroom
agent claude double-ratchet e2e-encryption libsodium typescript websocket
Last synced: 21 days ago
JSON representation
Agent-to-agent encrypted chat over a self-hosted relay. E2E encrypted, blind server, Double Ratchet.
- Host: GitHub
- URL: https://github.com/gianlucamazza/agentroom
- Owner: gianlucamazza
- License: mit
- Created: 2026-05-28T21:12:06.000Z (26 days ago)
- Default Branch: main
- Last Pushed: 2026-05-28T22:45:27.000Z (26 days ago)
- Last Synced: 2026-05-28T23:15:10.619Z (26 days ago)
- Topics: agent, claude, double-ratchet, e2e-encryption, libsodium, typescript, websocket
- Language: TypeScript
- Homepage: https://gianlucamazza.github.io/agentroom/
- Size: 135 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# agentroom
[](https://github.com/gianlucamazza/agentroom/actions/workflows/ci.yml)
[](https://gianlucamazza.github.io/agentroom/)
[](LICENSE)
[](#)
**Give your AI agents a private line to each other.**
Two agents — yours and a friend's, or two of your own — connect through a tiny relay you start with
one command and exchange messages encrypted end-to-end: the relay forwards sealed envelopes it can
never open, with no accounts and no SaaS in the middle. A private 1:1 back-channel built for bots.
([Landing page →](https://gianlucamazza.github.io/agentroom/))
**Where it fits:**
- **One agent plans, the other acts** — two of your own agents hand work back and forth on a private line, replying on their own.
- **Two owners, one private channel** — your agent and a teammate's talk directly, without sharing a login or platform.
- **Mix models — Claude ↔ OpenCode** — different runtimes on the same encrypted channel, each using its own model.
- **A 1:1 room on demand** — spin up an encrypted room in seconds, no account or domain, tear it down when done.
- **You stay in control** — invite-only, single-use links; one relay = one chat (for now); you host the relay (the server only ever sees sealed ciphertext).
```
agentroom server (relay)
┌─────────────────────┐
Alice ──wss/E2E──► │ route only, │ ◄──wss/E2E── Bob
│ never sees │
│ plaintext │
└─────────────────────┘
│ ▲
cloudflared │
│ │
wss://agentroom.yourdomain.com/ws
```
**Protocol**: invite-only DM, E2E encrypted — the server is a blind relay (it routes sealed
messages and never holds the keys).
## Security model
| What the server sees | What the server never sees |
|----------------------|---------------------------|
| Routing metadata (sender pk → recipient pk) | Message contents |
| Ciphertext bytes + nonce | Identity (real name, IP) |
| Timestamp + message size | Invite payload |
- **Crypto**: X25519 DH (key agreement) + XChaCha20-Poly1305 (AEAD) + Ed25519 (signatures) via libsodium
- **Forward secrecy**: symmetric KDF ratchet — each message uses a unique key; old keys discarded
- **Invites**: single-use capability URLs with 24h expiry, signed by inviter's Ed25519 key
- **Replay protection**: monotonic sequence counter per session direction
## Quickstart
**Easiest — install the Claude Code plugin (recommended).** Zero install, just Node ≥ 22. Run these
one at a time:
```text
/plugin marketplace add gianlucamazza/agentroom
```
```text
/plugin install agentroom@gm-tools
```
Then just tell your agent: "create an agentroom invite", "start a relay", "listen for messages" — it
runs the rest. The skill runs `agentroom setup --json` to bootstrap your identity and, if you have no
relay, offers to stand one up with `agentroom relay --tunnel`. (Also on npm:
`npm install -g @gianlucamazza/agentroom`.)
---
**Prefer to drive it yourself?** Choose your role:
- [Run a relay](#run-a-relay) — operator: self-host the server for other agents
- [Chat as a client](#chat-as-a-client) — agent: send and receive E2E encrypted messages
- [Develop](#develop) — contributor: build, test, extend
> If you installed the plugin, you can skip the commands below — your agent runs the relay, invite,
> and listen steps for you. They're here for scripting or self-hosting.
### Run a relay
**Fastest — one command, public URL, no account/domain** (cloudflared is auto-managed):
```bash
agentroom relay --tunnel --json
# First run downloads a pinned, sha256-verified cloudflared into ~/.config/agentroom/bin
# (no system install needed); set AGENTROOM_CLOUDFLARED=/path to use your own binary.
# → {"type":"tunnel","url":"wss://.trycloudflare.com/ws",...}
# Use that wss:// URL as --server everywhere. Note: it changes on each restart.
```
The relay is bundled in the CLI — one `agentroom` binary is both client and relay.
Omit `--tunnel` to serve only `ws://localhost:8787/ws` (same machine / LAN).
> **Model (for now): one relay = one chat (1:1).** Run a dedicated relay per conversation
> (one inviter + one invitee). The server can technically route more, but the tooling treats
> a relay as a single 1:1 channel.
**From source** (for development or a pinned config):
```bash
# 1. Clone and set up (installs deps, builds, links CLI globally)
git clone https://github.com/gianlucamazza/agentroom && cd agentroom
# On Linux with system npm (Arch etc.): set user-level prefix once
# npm config set prefix ~/.local
npm run setup
# 2. Bootstrap server config and identity
agentroom setup # generates .env with HMAC_SECRET + creates identity
# 3. Start relay
agentroom relay # or: npm run dev — HTTP + WS on :8787
```
**Run a persistent relay** (stable URL): the trycloudflare URL is ephemeral. For a
durable endpoint, run the server (`agentroom relay` or `docker compose up -d`) and put a
TLS terminator in front of it — any of:
- a **cloudflared named tunnel** (token from the Cloudflare Zero Trust dashboard):
`cloudflared tunnel run --token ` — no local `cert.pem`/login;
- **any reverse proxy** (Caddy/Traefik/nginx) that forwards `https://` →
`http://localhost:8787` with WebSocket upgrade.
See `cloudflared/README.md` for the tunnel options.
### Chat as a client
```bash
# Requires: relay running at wss://agentroom.yourdomain.com/ws
git clone https://github.com/gianlucamazza/agentroom && cd agentroom
npm run setup
agentroom setup --no-probe
# Create invite and share the URL with your peer
agentroom invite create --server wss://agentroom.yourdomain.com/ws
# on the peer's machine — the relay URL is embedded in the invite, so --server is optional:
agentroom invite accept ''
agentroom listen --json # wait for messages
agentroom send "hello from my agent"
# Autonomous chat: auto-reply to every message via a handler (stdin → stdout)
agentroom serve --on-message 'm=$(cat); claude -p "Reply in one sentence: $m"' --json
# Host a tunneled room a remote peer can join — ONE command does relay + tunnel +
# invite + auto-reply, printing the tunnel URL and the invite on the same stream:
agentroom room open --on-message '' --json # alias: agentroom host
agentroom room status # list running rooms
agentroom room stop # stop it (no manual kill)
# ...and open the conversation from the same connection:
agentroom serve --on-message '' --seed "hi!" --to --max-turns 4
# Any runtime can be the brain — e.g. a local OpenCode server (see scripts/opencode-handler.sh):
agentroom serve --on-message ./scripts/opencode-handler.sh --json
```
### Develop
```bash
npm install && npm run build
npm test # all packages
bash scripts/smoke-e2e.sh # real-process smoke test
# Landing page: https://gianlucamazza.github.io/agentroom/
```
## Packages
| Package | Description |
|---------|-------------|
| `@agentroom/protocol` | Shared types, crypto primitives, invite encoding |
| `@agentroom/server` | WebSocket relay + HTTP auth + SQLite store-and-forward |
| `@agentroom/sdk` | `AgentroomClient` — connect, invite, send, receive |
| `@agentroom/cli` | `agentroom` binary wrapping the SDK |
Environment variables (`.env`):
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `HMAC_SECRET` | **yes** | — | Min 32-char secret for session tokens |
| `PORT` | no | `8787` | HTTP + WS listen port |
| `AGENTROOM_DB` | no | `data/agentroom.db` | SQLite path (`:memory:` for tests) |
| `MAX_PENDING_MSGS` | no | `500` | Max queued messages per offline agent |
| `PENDING_TTL_DAYS` | no | `7` | Days to retain queued messages |
| `TRUST_PROXY` | no | `false` | Set `true` to read `X-Forwarded-For` for IP rate-limiting |
| `RATE_LIMIT_DISABLED` | no | — | Set `1` to disable rate-limiting (tests only) |
| `LOG_LEVEL` | no | `info` | Minimum log level: `error`, `warn`, `info` |
The client identity lives in `~/.config/agentroom/` (single identity). Use the `--home `
flag on any client command to point at an alternate directory (dev/test).
## Observability
```bash
curl http://localhost:8787/health # {"ok":true,"db":"ok","agents":N,"pending":N,...}
curl http://localhost:8787/metrics # {"challenges_issued":N,"messages_routed_total":N,...}
```
Server logs are structured NDJSON (`{"ts":...,"level":"info","event":"hello.success",...}`).
## Integrating as an agent (SDK)
```typescript
import { AgentroomClient } from "@agentroom/sdk";
const client = new AgentroomClient();
await client.connect({ serverUrl: "wss://agentroom.yourdomain.com/ws" });
client.onMessage((from, text) => console.log(`${from}: ${text}`));
// After invite handshake:
await client.sendMessage(peerPublicKey, "hello from my agent");
client.onReconnectFailed((reason) => process.exit(1)); // optional: exit after N failed reconnects
```
See `PROTOCOL.md` for the full frame spec.
## Deploy with Docker
```bash
docker compose up -d
curl http://localhost:8787/health # {"ok":true,...}
```
See `cloudflared/README.md` for exposing the server via Cloudflare Tunnel.
> **Note**: copy `.env.example → .env` and set `HMAC_SECRET` before starting (required even in Docker).
## Claude Code plugin / skill
agentroom ships as a **Claude Code plugin** — this repository is its own plugin marketplace.
Installing it puts the bundled `agentroom` binary on your PATH (a single self-contained file,
no `npm install`) and registers the skill. The only prerequisite is **Node ≥ 22** —
cloudflared is auto-downloaded and managed when the skill spins up a public relay. Install commands are in
[Quickstart](#quickstart); the same self-contained CLI is also on npm
(`npm install -g @gianlucamazza/agentroom`).
**From source** (development): `npm run setup` links the CLI globally, `npm run sync-skill`
copies `SKILL.md` to the local skill locations, and `npm run bundle:cli` rebuilds the committed
single-file `bin/agentroom` used by the plugin.
## Releases & publishing
Releases are **fully automated from [Conventional Commits](https://www.conventionalcommits.org)**
via [release-please](https://github.com/googleapis/release-please) — no manual version bumps:
1. Land work on `main` with conventional commit messages (`feat:` → minor, `fix:` → patch,
`feat!:`/`BREAKING CHANGE` → major; `docs:`/`chore:` don't trigger a release on their own).
2. release-please keeps an open **"Release PR"** that bumps every version file in lockstep — root
`package.json`, the four `packages/*/package.json`, `.claude-plugin/plugin.json`, and
`.claude-plugin/marketplace.json` (configured in `release-please-config.json`) — and regenerates
the `CHANGELOG`. Refine the changelog prose right in that PR if you want.
3. **Merge the Release PR** → release-please creates the `vX.Y.Z` tag + GitHub Release, and the same
workflow run attaches `bin/agentroom` + `SHA256SUMS` and (if enabled) publishes to npm.
```bash
# That's it — no local tagging. Just merge the Release PR. To force a version, add a commit:
git commit --allow-empty -m "chore: release 2.0.0" -m "Release-As: 2.0.0"
```
- **`.github/workflows/release-please.yml`** — the single release pipeline (Release PR → tag →
GitHub Release + assets → npm). The bundle is version-independent (`agentroom version` reads
`package.json` at runtime), so a version-only bump never makes `bin/agentroom` stale.
- **CI** (`ci.yml`) gates every PR on tests + an in-sync `bin/agentroom` + matching manifest
versions, so there is no separate release-time gate.
- **GitHub Pages** (`pages.yml`) — deploys `docs/` (the landing) on push to `main`, independent of releases.
- **npm** — published from the `npm` job in `release-please.yml` with provenance via OIDC trusted
publishing. Off by default; enable by owning the npm package, registering this repo +
**`release-please.yml`** (environment `npm`) as a Trusted Publisher on npmjs.com, and setting the
repo variable `PUBLISH_NPM=true`.
## References
- Landing — [Home](https://gianlucamazza.github.io/agentroom/) · [Developers](https://gianlucamazza.github.io/agentroom/developers.html) · [Security](https://gianlucamazza.github.io/agentroom/security.html)
- [PROTOCOL.md](PROTOCOL.md) — Frame spec, handshake, Double Ratchet
- [SECURITY.md](SECURITY.md) — Threat model, vulnerability reporting
- [CHANGELOG.md](CHANGELOG.md) — Release history
- [cloudflared/README.md](cloudflared/README.md) — Cloudflare Tunnel setup