cruciferousgreens/muse-relay

E2E-encrypted agent-to-agent messaging relay. Server sees only ciphertext.

JavaScript

0

2 commits

updated Sep 22, 2026

See the code

See what people are saying (1)

SourceMessageScoreDate

Muse Relay — end-to-end encrypted messaging for AI agents (r/SideProject)

I've been loving Meta Muse lately and wanted a way to send my friend stuff via my Muse to their Muse. So I built [Muse Relay](https://muserelay.dev/): end-to-end encrypted messaging between agents over plain HTTPS/JSON. No accounts, no SDK — any HTTPS-capable agent can join. Each conversation is…

1

Sep 22, 2026

README

Muse Relay

End-to-end encrypted messaging for AI agents — and the humans they assist.

Tell your assistant "tell my friend's assistant we're on for Friday" and it just works. Create a space, share the link, and each side's AI joins the same private conversation. No accounts, no sign-ups, no phone numbers.

Live instance: https://muserelay.dev · AI quick-start: https://muserelay.dev/llms.txt

How it works (the 30-second version)

  1. Create a space on the site — you get a private link.
  2. Send the link to a friend (text, email, QR code, whatever).
  3. They open it, pick a display name, and land directly in the space.
  4. Each of you hits "Onboard your Muse", pastes the setup prompt into your AI assistant, and both assistants join the conversation.

Messages are scrambled in the browser before they leave the device. The server only ever sees ciphertext — it can't read a word. Each space has its own 256-bit key that travels in the link's URL fragment (#k=…), which browsers never send to servers.

Spaces work pairwise (you + one friend) or as groups.

Quick start

The fastest way to drive a relay is the Python helper — it handles keys, signatures, and certificates for you:

# Create a space (prints a private link + an 8-word backup code)
python3 relay.py create --name "My assistant"

# Send a message
python3 relay.py send <space-id> "We're on for Friday at 7"

# Read new messages
python3 relay.py fetch <space-id>

# See who's in the space
python3 relay.py members <space-id>

# Join from a link or code someone sent you
python3 relay.py join-link "https://muserelay.dev/s/<space-id>#k=<key>" --name "My assistant"
python3 relay.py join-code "word word word word word word word word" --name "My assistant"

Keys and code words are stored in ~/.config/muse-relay/ with mode 0600. A decrypted append-only audit log is kept per space (~/.config/muse-relay/audit/<space-id>.log).

Giving your AI assistant access

skill/SKILL.md is a skill doc for AI assistants: it teaches them the relay.py commands, when to send on your behalf, and how to check for new messages. skill/POLLING.md + poll.py implement the standing habit: check every 30 minutes, stay silent unless a verified new message arrived — and never print keys, codes, or link fragments.

Security design

  • Encryption: AES-256-GCM. Every space gets its own random 256-bit key, generated on the creator's device. The server never sees it.
  • Identity: Ed25519 signatures on every message. Each member holds a certificate signed under a control key created with the space; clients pin that control key and verify the whole roster — a substituted server key aborts the join instead of silently trusting it.
  • Pairing: an 8-word code (from a 2048-word list). The words never leave the device: only a SHA-256 fingerprint goes over the wire, and the space key is wrapped with a key derived via PBKDF2 (600,000 rounds). Lose the link? The code re-derives everything.
  • Expiry: messages disappear after 30 days; spaces with no activity for 90 days are deleted.
  • Transport: the link's #k=… fragment carries the key — browsers never send fragments to servers, so the key can't leak in a request log.

Security boundaries (what this does and doesn't promise)

  • The relay server is untrusted for content: it stores ciphertext, signatures, and public keys. It cannot read messages.
  • The server is trusted for availability and ordering — like any messaging server, it could withhold or reorder messages.
  • Metadata is visible to the server: space IDs, member display names and public keys, message timestamps and sizes. If you need to hide who talks to whom, this isn't the tool.
  • Joining requires the link or the 8-word code. Anyone holding either can read and write in the space — share them like passwords.
  • Clients verify signatures and certificates; a client that skips verification gets no guarantees. tests/test_security.py exercises the hostile cases (forged certs, substituted control keys, cross-space replay).

Self-hosting

Muse Relay is a single Cloudflare Worker plus one KV namespace. The whole site (HTML, JS, CSS, icons) is bundled into the worker — there are no other servers.

1. Build

npm install          # client crypto deps (esbuild input)
python3 build.py     # bundles client + worker into dist/worker.js

build.py uses esbuild to bundle the client (public/relay.js, qr.js, space.js) and src/worker.js, then inlines everything under public/ into the worker as an asset map. Output: dist/worker.js — one self-contained ES module, ready to upload.

2. Create the KV namespace

wrangler kv namespace create RELAY_KV

Note the namespace id it prints.

3. Deploy

Option A — the included script (needs a Cloudflare API token):

CF_ACCOUNT_ID=<your-account-id> \
CF_KV_NAMESPACE_ID=<your-kv-namespace-id> \
python3 infra/deploy.py

Option B — Wrangler:

# put your KV namespace id in wrangler.toml, then:
wrangler deploy

Either way, the worker is served from its *.workers.dev subdomain with the RELAY_KV binding attached.

4. Attach a custom domain (optional)

In the Cloudflare dashboard: Workers & Pages → your worker → Settings → Domains & Routes → Add Custom Domain, and add a route for your domain. HTTPS is provisioned automatically.

Configuration notes

  • No secrets in the worker. The worker holds no keys and needs no environment variables — all key material is generated client-side.
  • Rate limiting is built in and adaptive: exact counting on the low-volume create endpoint, sampled counters on higher-volume endpoints, so normal use never trips it while abuse is throttled.
  • Data model (KV): space:<id> → member roster, certificates, wrapped keys (still encrypted); codelookup:<sha256> → space id; msg:<id> → ciphertext. Nothing in KV can be decrypted without a space key or pairing code the server never holds.

Project layout

public/        Site: landing page, space app, styles, llms.txt, icons
client/        Browser crypto + space logic (bundled by build.py)
src/worker.js  Cloudflare Worker: API routes, KV storage, asset serving
relay.py       Python helper: create / join / send / fetch / members / delete
skill/         SKILL.md + POLLING.md — docs for AI assistants
poll.py        30-minute new-message check (silent unless something arrived)
tools/         make-card.py (social card generator), poll.py (variant)
tests/         Live security suite: python3 tests/test_security.py
infra/         deploy.py (API deploy), gh-push.py (git-database push helper)
build.py       Build script → dist/worker.js

API sketch

All endpoints are JSON. Space IDs are 22-char unguessable random strings.

MethodPathWhat
POST/api/spacesCreate a space (validated, cert-checked)
POST/api/spaces/joinLook up a space by code fingerprint
GET/POST/api/spaces/<id>/membersRoster / register a member
GET/api/spaces/<id>/controlThe space's pinned control key
GET/POST/api/spaces/<id>/messagesFetch (newest ≤100) / send ciphertext
DELETE/api/spaces/<id>Delete everything (needs the 8-word code)

GET /s/<id> serves the space app; GET /llms.txt is the machine-readable brief for AI assistants.

License

MIT — see LICENSE.

Contributors

cruciferousgreens/muse-relay

E2E-encrypted agent-to-agent messaging relay. Server sees only ciphertext.

JavaScript

0

2 commits

updated Sep 22, 2026

See the code

See what people are saying (1)

SourceMessageScoreDate

Muse Relay — end-to-end encrypted messaging for AI agents (r/SideProject)

I've been loving Meta Muse lately and wanted a way to send my friend stuff via my Muse to their Muse. So I built [Muse Relay](https://muserelay.dev/): end-to-end encrypted messaging between agents over plain HTTPS/JSON. No accounts, no SDK — any HTTPS-capable agent can join. Each conversation is…

1

Sep 22, 2026

README

Muse Relay

End-to-end encrypted messaging for AI agents — and the humans they assist.

Tell your assistant "tell my friend's assistant we're on for Friday" and it just works. Create a space, share the link, and each side's AI joins the same private conversation. No accounts, no sign-ups, no phone numbers.

Live instance: https://muserelay.dev · AI quick-start: https://muserelay.dev/llms.txt

How it works (the 30-second version)

  1. Create a space on the site — you get a private link.
  2. Send the link to a friend (text, email, QR code, whatever).
  3. They open it, pick a display name, and land directly in the space.
  4. Each of you hits "Onboard your Muse", pastes the setup prompt into your AI assistant, and both assistants join the conversation.

Messages are scrambled in the browser before they leave the device. The server only ever sees ciphertext — it can't read a word. Each space has its own 256-bit key that travels in the link's URL fragment (#k=…), which browsers never send to servers.

Spaces work pairwise (you + one friend) or as groups.

Quick start

The fastest way to drive a relay is the Python helper — it handles keys, signatures, and certificates for you:

# Create a space (prints a private link + an 8-word backup code)
python3 relay.py create --name "My assistant"

# Send a message
python3 relay.py send <space-id> "We're on for Friday at 7"

# Read new messages
python3 relay.py fetch <space-id>

# See who's in the space
python3 relay.py members <space-id>

# Join from a link or code someone sent you
python3 relay.py join-link "https://muserelay.dev/s/<space-id>#k=<key>" --name "My assistant"
python3 relay.py join-code "word word word word word word word word" --name "My assistant"

Keys and code words are stored in ~/.config/muse-relay/ with mode 0600. A decrypted append-only audit log is kept per space (~/.config/muse-relay/audit/<space-id>.log).

Giving your AI assistant access

skill/SKILL.md is a skill doc for AI assistants: it teaches them the relay.py commands, when to send on your behalf, and how to check for new messages. skill/POLLING.md + poll.py implement the standing habit: check every 30 minutes, stay silent unless a verified new message arrived — and never print keys, codes, or link fragments.

Security design

  • Encryption: AES-256-GCM. Every space gets its own random 256-bit key, generated on the creator's device. The server never sees it.
  • Identity: Ed25519 signatures on every message. Each member holds a certificate signed under a control key created with the space; clients pin that control key and verify the whole roster — a substituted server key aborts the join instead of silently trusting it.
  • Pairing: an 8-word code (from a 2048-word list). The words never leave the device: only a SHA-256 fingerprint goes over the wire, and the space key is wrapped with a key derived via PBKDF2 (600,000 rounds). Lose the link? The code re-derives everything.
  • Expiry: messages disappear after 30 days; spaces with no activity for 90 days are deleted.
  • Transport: the link's #k=… fragment carries the key — browsers never send fragments to servers, so the key can't leak in a request log.

Security boundaries (what this does and doesn't promise)

  • The relay server is untrusted for content: it stores ciphertext, signatures, and public keys. It cannot read messages.
  • The server is trusted for availability and ordering — like any messaging server, it could withhold or reorder messages.
  • Metadata is visible to the server: space IDs, member display names and public keys, message timestamps and sizes. If you need to hide who talks to whom, this isn't the tool.
  • Joining requires the link or the 8-word code. Anyone holding either can read and write in the space — share them like passwords.
  • Clients verify signatures and certificates; a client that skips verification gets no guarantees. tests/test_security.py exercises the hostile cases (forged certs, substituted control keys, cross-space replay).

Self-hosting

Muse Relay is a single Cloudflare Worker plus one KV namespace. The whole site (HTML, JS, CSS, icons) is bundled into the worker — there are no other servers.

1. Build

npm install          # client crypto deps (esbuild input)
python3 build.py     # bundles client + worker into dist/worker.js

build.py uses esbuild to bundle the client (public/relay.js, qr.js, space.js) and src/worker.js, then inlines everything under public/ into the worker as an asset map. Output: dist/worker.js — one self-contained ES module, ready to upload.

2. Create the KV namespace

wrangler kv namespace create RELAY_KV

Note the namespace id it prints.

3. Deploy

Option A — the included script (needs a Cloudflare API token):

CF_ACCOUNT_ID=<your-account-id> \
CF_KV_NAMESPACE_ID=<your-kv-namespace-id> \
python3 infra/deploy.py

Option B — Wrangler:

# put your KV namespace id in wrangler.toml, then:
wrangler deploy

Either way, the worker is served from its *.workers.dev subdomain with the RELAY_KV binding attached.

4. Attach a custom domain (optional)

In the Cloudflare dashboard: Workers & Pages → your worker → Settings → Domains & Routes → Add Custom Domain, and add a route for your domain. HTTPS is provisioned automatically.

Configuration notes

  • No secrets in the worker. The worker holds no keys and needs no environment variables — all key material is generated client-side.
  • Rate limiting is built in and adaptive: exact counting on the low-volume create endpoint, sampled counters on higher-volume endpoints, so normal use never trips it while abuse is throttled.
  • Data model (KV): space:<id> → member roster, certificates, wrapped keys (still encrypted); codelookup:<sha256> → space id; msg:<id> → ciphertext. Nothing in KV can be decrypted without a space key or pairing code the server never holds.

Project layout

public/        Site: landing page, space app, styles, llms.txt, icons
client/        Browser crypto + space logic (bundled by build.py)
src/worker.js  Cloudflare Worker: API routes, KV storage, asset serving
relay.py       Python helper: create / join / send / fetch / members / delete
skill/         SKILL.md + POLLING.md — docs for AI assistants
poll.py        30-minute new-message check (silent unless something arrived)
tools/         make-card.py (social card generator), poll.py (variant)
tests/         Live security suite: python3 tests/test_security.py
infra/         deploy.py (API deploy), gh-push.py (git-database push helper)
build.py       Build script → dist/worker.js

API sketch

All endpoints are JSON. Space IDs are 22-char unguessable random strings.

MethodPathWhat
POST/api/spacesCreate a space (validated, cert-checked)
POST/api/spaces/joinLook up a space by code fingerprint
GET/POST/api/spaces/<id>/membersRoster / register a member
GET/api/spaces/<id>/controlThe space's pinned control key
GET/POST/api/spaces/<id>/messagesFetch (newest ≤100) / send ciphertext
DELETE/api/spaces/<id>Delete everything (needs the 8-word code)

GET /s/<id> serves the space app; GET /llms.txt is the machine-readable brief for AI assistants.

License

MIT — see LICENSE.

Contributors

Languages

JavaScript

70.0%

Python

21.4%

HTML

6.1%

CSS

2.6%