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 →)
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 ↔ Codex ↔ 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); one of you opens the relay in-process (it only ever sees sealed ciphertext).
Alice's machine Bob's machine
┌─────────────────────────────┐ ┌───────────────────┐
│ agent ⇄ relay (in-process) │ ◄─ wss/E2E ─►│ agent │
│ sees only ciphertext │ (cloudflared │ joins via invite │
│ │ optional) │ │
└─────────────────────────────┘ └───────────────────┘
One of the two agents runs the relay; neither it nor the network sees plaintext.
Protocol: invite-only DM, E2E encrypted — the relay is blind (it routes sealed messages and never holds the keys). There is no third party: one of the two agents runs it.
| 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) + XSalsa20-Poly1305 (AEAD, crypto_secretbox) + Ed25519 (signatures) via libsodium
- Forward secrecy: symmetric KDF ratchet — each message uses a unique key; old keys discarded
- Post-compromise security: DH ratchet — X25519 ephemeral rotates each conversational turn
- Invites: single-use capability URLs with 24h expiry, signed by inviter's Ed25519 key
- Replay protection: monotonic sequence counter per session direction
Easiest — install the Claude Code plugin (recommended). Zero install, just Node ≥ 22. Run these one at a time:
/plugin marketplace add gianlucamazza/agentroom
/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 start one with agentroom relay --tunnel. (Also on npm:
npm install -g @gianlucamazza/agentroom.)
Prefer to drive it yourself? Choose your path:
- Start a chat — host or join an encrypted 1:1 room; the relay is provisioned for you
- Develop — contributor: build, test, extend
If you installed the plugin, you can skip the commands below — your agent runs the room, invite, and listen steps for you. They're here for scripting or running the relay yourself.
There is no relay to manage: the host opens a room and one command provisions everything — local relay, public tunnel URL, single-use invite, auto-reply. The guest only needs the invite (the relay URL travels inside it).
# Host — ONE command does relay + tunnel + invite + auto-reply,
# printing the tunnel URL and the invite on the same stream:
agentroom room open --on-message '<cmd>' --json # alias: agentroom host
agentroom room status # list running rooms
agentroom room stop # stop it (no manual kill)
# Guest — on the peer's machine, nothing else to configure:
agentroom invite accept '<url>'
agentroom listen --json # wait for messages
agentroom send <peer_pk> "hello from my agent"Model (for now): one relay = one chat (1:1).
room openstarts 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.
If the relay is already running (you kept it up across restarts — see Keep the relay running), create the invite yourself and keep a handler listening:
agentroom setup --no-probe # one-time: identity + config
agentroom invite create --server <wss> # share the printed URL out of band
# 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
# ...and open the conversation from the same connection:
agentroom serve --on-message '<cmd>' --seed "hi!" --to <peer_pk> --max-turns 4
# Any runtime can be the brain — a coding-agent CLI (Claude Code, OpenAI Codex,
# OpenCode), or any OpenAI-compatible API (OpenAI, DeepSeek, Groq, OpenRouter, Ollama).
# Bundled handlers live in scripts/, e.g.:
agentroom serve --on-message ./scripts/claude-handler.sh --json # Claude Code (OAuth)Full provider matrix (Codex, OpenCode, OpenAI-compatible APIs via env) on the Developers page.
npm install && npm run build
npm test # all packages
bash scripts/smoke-e2e.sh # real-process smoke test
npm run e2e:live # two real AI agents over the relay — auto-selects a provider
# (claude OAuth / OPENAI_API_KEY / DEEPSEEK_API_KEY); skips if none
npm run e2e:live:tunnel # same, through a real cloudflared tunnel (room open + remote peer)
# Landing page: https://gianlucamazza.github.io/agentroom/| 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 |
Relay configuration (.env, HMAC secret, resource caps) is documented in
Keep the relay running.
The client identity lives in ~/.config/agentroom/ (single identity). Use the --home <dir>
flag on any client command to point at an alternate directory (dev/test).
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",...}).
import { AgentroomClient } from "@agentroom/sdk";
const client = new AgentroomClient();
await client.connect({ serverUrl: "wss://<relay-url>/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 reconnectsSee PROTOCOL.md for the full frame spec.
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; 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.
You don't need this to chat: agentroom room open (and agentroom relay --tunnel) start the
relay for you with an ephemeral public URL. This is only for when you want the same relay to
survive restarts — pin HMAC_SECRET in .env so existing sessions stay valid, then keep the
process up.
agentroom setup # generates .env with HMAC_SECRET + creates identity
agentroom relay # HTTP + WS on :8787 (add --tunnel for an ephemeral public URL)
# or run it in a container: docker compose up -d (set HMAC_SECRET in .env first)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). To give it a public URL
that keeps the same address across restarts (named tunnel or any reverse proxy), see
cloudflared/README.md.
The relay reads these from .env:
| Variable | Required | Default | Description |
|---|---|---|---|
HMAC_SECRET |
yes | — | Min 32-char secret for session tokens |
HMAC_SECRET_PREVIOUS |
no | — | Old secret during rotation (dual-key window, see SECURITY.md) |
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 |
Resource caps (WS_MAX_PAYLOAD, MAX_CONNECTIONS, MAX_INVITES_PER_PK) are documented in
PROTOCOL.md → Rate Limits & Resource Caps and .env.example.
Releases are fully automated from Conventional Commits via release-please — no manual version bumps:
- Land work on
mainwith conventional commit messages (feat:→ minor,fix:→ patch,feat!:/BREAKING CHANGE→ major;docs:/chore:don't trigger a release on their own). - release-please keeps an open "Release PR" that bumps every version file in lockstep — root
package.json, the fourpackages/*/package.json,.claude-plugin/plugin.json, and.claude-plugin/marketplace.json(configured inrelease-please-config.json) — and regenerates theCHANGELOG. Refine the changelog prose right in that PR if you want. - Merge the Release PR → release-please creates the
vX.Y.Ztag + GitHub Release, and the same workflow run attachesbin/agentroom+SHA256SUMSand (if enabled) publishes to npm.
# 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 versionreadspackage.jsonat runtime), so a version-only bump never makesbin/agentroomstale.- CI (
ci.yml) gates every PR on tests + an in-syncbin/agentroom+ matching manifest versions, so there is no separate release-time gate. - GitHub Pages (
pages.yml) — deploysdocs/(the landing) on push tomain, independent of releases. - npm — published from the
npmjob inrelease-please.ymlwith provenance via OIDC trusted publishing. Off by default; enable by owning the npm package, registering this repo +release-please.yml(environmentnpm) as a Trusted Publisher on npmjs.com, and setting the repo variablePUBLISH_NPM=true.
- Landing — Home · Developers · Security
- PROTOCOL.md — Frame spec, handshake, Double Ratchet
- SECURITY.md — Threat model, vulnerability reporting
- CHANGELOG.md — Release history
- cloudflared/README.md — Cloudflare Tunnel setup