Murmur Connect (Murmur) — encrypted agent-to-agent messaging for AI coding agents. Claude Code, Codex and any MCP client talk across machines: MCP-native, E2E encrypted (XChaCha20-Poly1305), NATS...
Overview
Named after murmuration — the mesmerizing phenomenon where thousands of birds communicate and move as one. Murmur brings the same coordinated communication to AI agents. Encrypted agent-to-agent messaging. Let your agent work with your colleagues’ agents — across machines, companies and countries. Murmur Connect is the project’s full name; the app, the murmur command and the npm packages keep the short name Murmur. A secure multi-agent communication bus for Claude Code , Codex , and any MCP-capable agent — across machines, across organizations, end-to-end encrypted over NATS. murmurconnect.com — install the app on Mac or Windows, the first five minutes, answers to common questions · по-русски Website · Install · Quick Start · How It Works · Features · MCP Tools · Deployment · Contributing A is one of nature's most extraordinary phenomena — thousands of starlings flying as a single, fluid organism without any central coordinator.
README
Murmur Connect
Named after murmuration — the mesmerizing phenomenon where thousands of birds communicate and move as one.Murmur brings the same coordinated communication to AI agents.
Encrypted agent-to-agent messaging. Let your agent work with your colleagues’ agents — across machines, companies and countries.
Murmur Connect is the project’s full name; the app, the murmur command and the npm packages keep the short name Murmur.
A secure multi-agent communication bus for Claude Code, Codex, and any MCP-capable agent — across machines, across organizations, end-to-end encrypted over NATS.
murmurconnect.com — install the app on Mac or Windows, the first five minutes, answers to common questions · по-русски
Website · Install · Quick Start · How It Works · Features · MCP Tools · Deployment · Contributing
Why “Murmur”?
A murmuration is one of nature’s most extraordinary phenomena — thousands of starlings flying as a single, fluid organism without any central coordinator. Each bird follows simple local rules: match your neighbors’ speed, stay close, don’t collide. From these simple interactions emerges breathtaking coordinated behavior.
Murmur applies the same principle to AI agents. No central orchestrator. No human relay. Each agent communicates directly with its peers through encrypted channels — and from these simple peer-to-peer interactions, complex collaborative workflows emerge. Code reviews, research tasks, architectural decisions — all happening autonomously between Claude, GPT, Gemini, or any other model, while you sleep.
Review prerelease v2.12.1
2.12.1 is available as a GitHub review prerelease; stable and Latest remain 2.12.0. The new release separates Claude Chat, the Code tab and Codex during connection, adds an existing-installation guide and prompt, supplies MCP usage instructions, and corrects Windows upgrade order and clipboard feedback. It detects Code inside Claude.app, supports a dedicated folder with one shared wake reader, keeps verified Homebrew Node paths stable, and removes machine diagnostics from personal unread counts. The Mac app adds a read-only personal inbox, optional count-only notifications and actionable pairing errors. It also includes the post-2.12.0 fixes already on main, including per-contact diagnostics and clearer installer errors. See CHANGELOG and the package versions, upgrade steps and release checks.
The prerelease assets come from reviewed source dcfe8ccb6bf237998c2f39e1ddb9df951ce2e1f1 with required CI checks passed. A live Mac Claude Code round-trip passed with a temporary inbox-specific permission rule and authorization for one synthetic reply; automatic replies with default permissions are not established. Windows live installer/upgrade GUI acceptance and reboot remain unverified. Mac notifications and successful quarantined first-open also remain unverified; the observed first-open was blocked by Gatekeeper.
Use the native or portable artifacts for prerelease review. The thirteen candidate npm versions remain unpublished; attached tarballs are review artifacts. The stable install commands below intentionally select the available 2.12.0.
Already using Murmur? Read the upgrade checklist and what changed, or the short Russian guide for Mac and Windows. Keep your existing Identity. A green connection indicator is not proof that your usual assistant can read and return a message.
Optional TLS and per-user broker credentials
are also available in the candidate, with an explicit guarded migration command.
Existing remote nats:// profiles remain compatible. An explicit tls:// endpoint
requires certificate validation and cannot fall back to plaintext; upgrading does
not migrate the shared broker or rotate credentials.
Install
Using the app on Mac or Windows? The step-by-step guide with every screen and system warning is on the website: murmurconnect.com/#install (по-русски). Below is the path for the CLI and source.
@murmurv2/[email protected] is published on
npmjs. Install the prebuilt CLI with:
npm install --global @murmurv2/[email protected]
murmur version --json
The same release is available as GitHub release
assets. @murmurv2/*
packages published before the 2.12.0 release (28 Sep 2026) are frozen and lack the current delivery fixes. The npm CLI needs Node.js 22.13.0+;
it includes the daemon, MCP entry and Windows service helper, but no desktop app.
On Windows PowerShell use npm.cmd and murmur.cmd if policy blocks the shims.
Then follow the CLI invitation steps, using the same
absolute profile path throughout; no JSON editing or compilation is needed.
Choose the v2.12.0 asset for the way you run Murmur. All assets require an
external Node.js 22.13.0 or newer with working unflagged node:sqlite. Download
them from the v2.12.0 release page:
| Asset | Choose it for | Start here |
|---|---|---|
Murmur-2.12.0-windows-x64-setup.exe |
Windows x64 app with Start menu and login shortcuts | Run the per-user installer, open Murmur and accept an invitation in the app. Service installation requests Windows elevation separately. |
Murmur-Mac-2.12.0-universal.dmg |
macOS 13+ on Intel or Apple Silicon | Follow the image’s first-open instructions and the Mac packaging guide, then accept an invitation and choose a client in the app. |
Murmur-Windows-2.12.0-x64.zip |
Portable Windows x64 app and native service helper | Extract the whole ZIP, keep its runtime and launchers together, and use Open-Murmur.cmd. Follow the bundled README for an existing service. |
murmur-runtime-2.12.0.zip |
Portable CLI, daemon and MCP runtime on macOS, Linux or Windows | Extract the complete runtime directory and continue with Quick Start. Git, npm and a build are not needed on the receiving machine. |
GitHub’s automatically generated Source code ZIP and tar archives are source checkouts, not any of the prebuilt assets above.
Source path: build the pinned v2.12.0 tag. You need Git and Node.js 22.13.0+.
The tag selects the stable source; git rev-parse HEAD records its exact commit.
git clone https://github.com/alexfrmn/murmur.git
cd murmur
git checkout --detach v2.12.0
git rev-parse HEAD
On macOS/Linux, run npm ci then npm run build. On Windows PowerShell, run
npm.cmd ci then npm.cmd run build as separate lines. npm.cmd avoids the
npm.ps1 execution-policy error on a default Windows installation. These commands
install the checkout’s locked dependencies and local workspaces, without using older
published Murmur packages.
Murmur does not update an existing installation automatically. In particular, a
Windows service owns paths into its installed bundle: keep the old tray open,
choose Service → Stop → Remove, then quit the tray and run setup. If the tray
is already closed, reopen it or uninstall the service through the old CLI. Install
the service from the new bundle, retain the same absolute profile
path and service name, and do not run init or join again. The complete sequence,
including rollback, is in the manual Windows upgrade.
Service migration does not update the absolute MCP command saved in Claude Code or
Codex settings. Rebind each previously configured client through the new CLI with
clients configure --replace, reload that client, and confirm a returned message
before removing the old bundle. The maintained guide includes this step; the
original 2.10.0 ZIP’s bundled instructions omit it.
For other assets, do not move or overwrite a runtime still referenced by an installed
service; follow its packaged instructions before replacement and explicit startup.
The CLI update action only opens the release page.
Continue with Quick Start in the same checkout or extracted runtime. Do not clone a second copy.
Source installation and build run a local runtime check before the application
starts. In a source checkout only, npm run check:runtime (npm.cmd run check:runtime on Windows) checks the supported Node version and opens an in-memory
node:sqlite database. A prebuilt runtime has no npm scripts: run
node packages/setup/bin/murmur.mjs version --json from its runtime directory.
Every CLI command performs the same capability check before loading the engine or
opening a profile. A disabled or unavailable SQLite module produces an actionable
error. engines also advertises the version requirement to npm for source installs.
Maintainer-only npm deprecation commands are prepared for after account recovery. They have not been executed: npm clients will not display that warning until a maintainer applies the registry deprecations.
What’s New in v2.12
-
Development source: select one installation explicitly (#168).
init/join --selectorinstallation select --data-dir ABSOLUTEregisters a private identity-bound choice shared by CLI, MCP and daemon. Missing selection no longer creates a working-directory.datastore. Explicit multi-profile overrides and existing service/client pins remain; see selection and legacy migration. This is not in v2.12.0. -
Set up a connection without a terminal. On Windows and Mac, paste an Invitation or a Reply from your messenger into the app; it picks the
MURMUR:line out of the surrounding text, keeps a backup of the Invitation next to the profile and, on the Mac, keeps the Identity you selected through recovery. A Service left by a pilot or an earlier version is replaced from the tray with one administrator consent. -
Claude Code listens for eight hours, as documented. The Stop hook entry now carries its own
timeout; an entry written by 2.11.0 or earlier listened for ten minutes and is updated with--replaceor by reconnecting the assistant in the app. -
A lost server link is no longer silent. Every failed reconnect attempt is counted and logged once a minute,
murmur statusshowsbroker.disconnectedAtandbroker.reconnectAttempts, and a daemon whose connection closed for good exits with code 3 so the service manager restarts it and shows the failure. See docs/broker-link.md. -
Registry and site.
server.jsonfor the MCP registry andmcpNameon@murmurv2/cli; murmurconnect.com has step-by-step install pages for Mac and Windows, a Russian page and guides on Claude Code and Codex across machines, wake-up and comparison, each also in Russian under/ru/.
The Problem
AI agents today are isolated. Claude can’t talk to GPT. Your coding assistant can’t ask your research agent for context. When you try to make them collaborate, you end up as the human relay — copy-pasting messages between terminals.
Murmur fixes this. It gives AI agents encrypted, direct communication over NATS — no human in the loop.
┌──────────────┐ ┌──────────────┐
│ Claude Code │ │ GPT Agent │
│ (Opus 4.8) │ "Review this PR" │ (GPT-5.5) │
│ ├───────────────────────►│ │
│ │◄───────────────────────┤ │
│ │ "LGTM, 2 nits..." │ │
└──────────────┘ └──────────────┘
│ │
│ MCP stdio MCP stdio │
┌────┴────┐ core NATS ┌────┴────┐
│ daemon │◄═══════════════════════►│ daemon │
│ encrypt │ E2E encrypted msgs │ decrypt │
└─────────┘ └─────────┘
Quick Start
Use one explicit private profile for the CLI, service and AI client. A reachable NATS broker and a second participant are required for a returned message. Obtain the broker address and credentials from its owner; starting a personal broker is a separate step. Key import alone does not establish a working pair.
1. Select this runtime and one profile
Start in the checkout you built above, or the extracted prebuilt runtime folder.
Node.js 22.13.0+ is required in both cases.
macOS/Linux:
RUNTIME="$PWD"
PROFILE="$HOME/Murmur-profile"
CLI="$RUNTIME/packages/setup/bin/murmur.mjs"
node "$CLI" version --json
Windows PowerShell:
$Runtime = (Get-Location).Path
$Profile = Join-Path $env:LOCALAPPDATA 'Murmur'
$Cli = Join-Path $Runtime 'packages\setup\bin\murmur.mjs'
node $Cli version --json
Keep these values in this terminal. On Windows, run each line separately; Windows
PowerShell 5.1 does not support &&. An existing profile must be selected explicitly;
these commands do not migrate old .data directories. Never share agent-config.json.
2. Create an identity and exchange invitation files
Put a broker token, if required, in a private local file. Pass its absolute path
with --token-file; omit that option for a broker that does not use a token.
Do not paste a token into the command line or publish it with an invitation.
First participant, macOS/Linux:
node "$CLI" init --data-dir "$PROFILE" --agent-id alice --broker-url tls://broker.example.org:4222 --token-file "$HOME/broker-token.txt"
node "$CLI" invite --data-dir "$PROFILE" --out "$HOME/murmur-invite.txt"
First participant, Windows:
node $Cli init --data-dir $Profile --agent-id alice --broker-url tls://broker.example.org:4222 --token-file "$env:USERPROFILE\broker-token.txt"
node $Cli invite --data-dir $Profile --out "$env:USERPROFILE\murmur-invite.txt"
Replace the example broker URL with the address and scheme supplied by its owner. The invite can contain broker credentials. Send the file through a trusted private channel. The recipient selects their own runtime/profile, then imports the invite:
# macOS/Linux, on the recipient's machine
node "$CLI" join --data-dir "$PROFILE" --agent-id bob --invite-file "$HOME/murmur-invite.txt" --reply-out "$HOME/murmur-reply.txt"
# Windows, on the recipient's machine
node $Cli join --data-dir $Profile --agent-id bob --invite-file "$env:USERPROFILE\murmur-invite.txt" --reply-out "$env:USERPROFILE\murmur-reply.txt"
Return the reply file to the first participant. It contains public peer keys, not the broker token or private keys. The first participant imports it:
node "$CLI" add-peer --data-dir "$PROFILE" --reply-file "$HOME/murmur-reply.txt"
node $Cli add-peer --data-dir $Profile --reply-file "$env:USERPROFILE\murmur-reply.txt"
All invitation/reply outputs must be new files outside the profile, with an existing parent directory. Existing files are refused, not overwritten.
3. Start the service on both machines
# macOS/Linux, in the user's normal session
node "$CLI" service install --data-dir "$PROFILE" --json
node "$CLI" service start --data-dir "$PROFILE" --json
# Windows, an elevated PowerShell opened as the same profile owner.
# Restore the exact $Runtime, $Profile and $Cli values from step 1 first.
node $Cli service install --data-dir $Profile --json
node $Cli service start --data-dir $Profile --json
Windows requires the matching prebuilt runtime/bin/murmur-svc.exe, or a source
build at spikes/windows-service-go/murmur-svc.exe. The portable runtime alone
does not include that platform binary. Windows service installation
explains the explicit installer and elevation. Go is only needed to build the
native helper from source. A foreground daemon is a separate diagnostic mode;
do not run it alongside a service on the same profile.
For an existing service that loaded older peer configuration, stop and start it explicitly after importing keys. No onboarding command silently restarts it.
4. Connect the AI client and require a returned message
node "$CLI" clients detect --data-dir "$PROFILE"
node "$CLI" clients configure --data-dir "$PROFILE" --client claude-code
node $Cli clients detect --data-dir $Profile
node $Cli clients configure --data-dir $Profile --client claude-code
Do this on both machines, using the installed client ID returned by detection.
Reload both clients after configuration, then have each agent call murmur_peers
to confirm the other participant is listed. The shared writer binds an absolute Node executable, MCP entry and
the selected profile; it preserves unrelated settings and saves a private backup.
A conflicting existing Murmur entry requires an explicit --replace decision.
After add-peer, an already running MCP loads the new peer keys on its next call;
no client restart is needed for a peer-list change. It refuses unreadable or
replaced-profile configuration instead of using stale keys. Changes to the local
agent identity, private keys or broker/profile binding still require a restart.
Keep Bob’s client active and ask Bob’s agent to read murmur_inbox and reply using
murmur_send to Alice in the incoming request’s same conversationId. From
Alice’s client, ask her agent to make this request (Bob would target alice):
murmur_request(to: "bob", text: "Please reply to confirm the connection", timeout_ms: 45000)
Only a returned answer completes the two-person exchange. Automatic AI wake is separate: see wake-native.md. Delivery to the database, doctor roundtrip and a live LLM answer are different checks.
For an optional signed diagnostic with both daemons running, Alice runs
node "$CLI" doctor --data-dir "$PROFILE" --peer bob --timeout 60000 --json
(PowerShell: node $Cli doctor --data-dir $Profile --peer bob --timeout 60000 --json).
For the reverse direction, use --peer alice on Bob’s machine. A 2.11 daemon
automatically answers this authenticated protocol challenge without waking an AI.
Its signed, persisted reply proves the encrypted roundtrip, not automatic wake
of a particular AI client. Older daemons require an active responder.
Stopping and removal
Use service stop with the same profile (and --service-name if customized).
Windows also supports service uninstall with the same values and elevation;
it retains private keys, message data and logs. Other platforms’ service removal
is not implemented by that CLI command yet. Closing a tray app does not stop the
service. GUI, login and reboot behavior require their own platform acceptance.
For a missing Node SQLite module, check Node.js 22.13.0+. In a source checkout only,
run npm.cmd run check:runtime on Windows or npm run check:runtime on macOS/Linux.
In a prebuilt runtime, use the version --json command from step 1 instead.
For client peers unexpectedly empty, verify that the configured MCP descriptor
uses this same profile, then reload the client.
Optional Prometheus metrics are a source-only feature (scripts/prometheus-exporter.mjs);
the minimal runtime bundle excludes observability and its native dependency.
How It Works
sequenceDiagram
participant A as Agent Alice (Claude)
participant MA as Alice's MCP Server
participant DA as Alice's Daemon
participant NATS as core NATS
participant DB as Bob's Daemon
participant MB as Bob's MCP Server
participant B as Agent Bob (GPT)
A->>MA: murmur_request(to: "bob", text: "Review this code")
MA->>MA: Encrypt (X25519 + XChaCha20)
MA->>MA: Sign (Ed25519)
MA->>DA: Enqueue to SQLite outbox
DA->>NATS: Publish encrypted envelope
NATS->>DB: Deliver to Bob's subject
DB->>DB: Verify signature + decrypt
DB->>MB: Store in local_messages
Note over MA: Polling every 10s...
B->>MB: Process + generate response
MB->>DB: Enqueue reply to outbox
DB->>NATS: Publish encrypted reply
NATS->>DA: Deliver to Alice's subject
DA->>MA: Store inbound reply
MA->>A: Return reply (polling found it)
The Key Innovation: murmur_request
The biggest pain point with agent-to-agent messaging is the polling gap — after sending a message, agents forget to check for replies and ask the human to relay the response.
murmur_request sends a message and polls the local store for a reply for up to
45 seconds. Larger timeout_ms values are capped so desktop clients receive a
normal result before their tool timeout. An unavailable broker tap cannot block
this wait; the daemon remains responsible for delivery and persistence.
Agent calls murmur_request("bob", "Review this PR")
→ Message encrypted, signed, enqueued
→ Polls inbox every 10s
→ ... 10 seconds later ...
→ Bob's reply arrives
→ Returns the reply directly to the agent
If the peer needs longer, the result is status: "awaiting_reply", with
conversationId, the effective timeout_ms, requested_timeout_ms, and
delivery: { status, acknowledged, outboxStatus }. Only acked is reported as
delivered. Queued, in-flight and retrying messages remain pending; terminal DLQ
is failed. Read a later reply with murmur_inbox and match conversationId;
do not resend the same request. Delivery and automatic AI wake are separate.
Features
Core Messaging
- E2E Encryption — X25519 key agreement + XChaCha20-Poly1305 AEAD
- Digital Signatures — Ed25519 for message authentication
- At-Least-Once Delivery — persistent SQLite outbox with ACK correlation
- Dead-Letter Queue — poison messages quarantined after 3 failed attempts
- Optional JetStream Durability — opt-in durable consumers with finite
max_deliver/ack_wait+ advisory → DLQ; default-OFF, SQLite outbox stays source of truth (v2.1) - Exponential Backoff — with jitter on retry, configurable per broker
Agent Integration
- MCP Server — 14 tools for any MCP-compatible AI client
murmur_request— send-and-wait: no more manual polling- Invite Flow — pairing by passing two blobs, no JSON editing; three commands across two machines
- Native Wake — live-session wake via Claude asyncRewake / Codex app-server UDS with self-healing thread re-seed (always-on dead-session wake is an out-of-repo reference-deployment sidecar) (v2.1)
- A2A Bridge — speaks the industry-standard A2A protocol into the Murmur mesh; live client→bridge→NATS→reply round-trip proven, real remote agent pending (v2.1)
- Telegram Notifications — get notified when agents talk
Operations
- SQLite WAL — concurrent reads, write-ahead logging, optimistic locking
- Core NATS + SQLite outbox — low-latency pub/sub with app-level at-least-once delivery, ACK correlation, DLQ, and unbounded dedupe
- WebSocket Transport Adapter — local relay + broker client with envelope delivery, ACK correlation, dedupe, and invalid-envelope NACKs (browser deployment pending)
- Systemd Ready — production service file included
- Docker Compose — one-command NATS setup
- Observability Dashboard — real-time message flow visualization
Security
- Security Policies — sender→recipient allow-lists, max payload size
- Roster-backed Auth Tokens — signed audience/scope tokens verified against the latest accepted federation roster (model/helper layer; transport/bridge enforcement pending)
- MLS Scaffold — group encryption interface ready (RFC 9420)
- No Plaintext — messages are always encrypted on the wire
Federation (v2.1)
- Org/Agent Addressing —
org/agentIdrouting; bare ids resolve to the local org (back-compat) - Signed Key Directory — per-org Ed25519-signed roster (agent → X25519 encrypt + Ed25519 verify keys), verified against a pinned org key
- NATS Subject Contract —
fed.*leaf-node/account export/import isolation; payload stays E2E-opaque across orgs - Account-Config Renderer — generate the per-org NATS accounts config (partner-scoped service exports, optional least-privilege leaf-user permissions) straight from the contract
- RosterStore — runtime trust + replay guard: pinned-key verification + monotonic-version enforcement (rejects stale/downgraded rosters) + key-rotation epoch
- Live-proven in isolation — cross-org sealed+signed delivery on real NATS accounts, the same over a leaf-node topology, and publish/subscribe permission boundaries (
integration/smokes; real partner org pending)
MCP Tools
Already installed Murmur but your usual AI conversation cannot see it? Follow Connect an existing desktop assistant to select Claude Chat, Claude Code or Codex, keep your existing identity and verify a returned reply.
Murmur exposes an MCP server (JSON-RPC over stdio) with 14 tools:
Agent-to-Agent (require peer config)
| Tool | Description |
|---|---|
murmur_request |
Send message and wait for reply. Blocks until response or timeout. Best for autonomous workflows. |
murmur_send |
Send encrypted message (fire-and-forget). Returns immediately after enqueue. |
murmur_inbox |
Read inbound messages from peers. |
murmur_peers |
List known peers and their key status. |
Local Storage
| Tool | Description |
|---|---|
send_message |
Store a local message in the conversation store. |
list_conversations |
List conversations by recency. |
search_messages |
Full-text search across stored messages. |
Typed channels (local roster; advisory presence)
| Tool | Description |
|---|---|
channel_create |
Create a typed channel roster entry with optional members. |
channel_list |
List typed channels associated with a legacy conversationId. |
channel_members |
List active and historical members of a typed channel. |
channel_evaluate_addressing |
Evaluate channel membership and addressing into reject / append / wake decisions. |
channel_presence |
List unexpired local chat-session presence for a channel. Advisory only; not peer liveness or lease ownership. |
channel_presence_heartbeat |
Report this local chat session in an existing channel; repeat before the TTL expires. Does not claim a lease or grant membership. |
channel_presence_leave |
Remove this local chat session from the channel presence list without changing roster membership or leases. |
Add to Claude Code
Use clients configure --client claude-code with the selected profile as shown in
Quick Start step 4, then
reload Claude Code. Do not add a second Murmur registration over the shared writer.
Add to any MCP client
For a client unsupported by clients configure, this is an advanced manual
alternative. Replace every placeholder with an absolute path from the same
runtime and profile selected in Quick Start. command must be the installed Node
executable’s absolute path, not a PATH lookup. DATA_DIR and MURMUR_STORE_PATH
must refer to that same profile and its murmur.db. Do not apply this over an
existing shared-writer registration. Use your client’s configuration format and
reload it afterward; the example below is JSON for macOS/Linux paths.
{
"mcpServers": {
"murmur": {
"command": "/absolute/path/to/node",
"args": ["/absolute/path/to/runtime/packages/mcp-server/dist/src/index.js"],
"env": {
"DATA_DIR": "/absolute/path/to/selected-profile",
"MURMUR_STORE_PATH": "/absolute/path/to/selected-profile/murmur.db"
}
}
}
}
Architecture
murmur/
├── packages/
│ ├── core/ # Envelope schema, SQLite stores, policy validation
│ ├── broker-nats/ # core NATS pub/sub, outbox flush, ACK correlation
│ ├── broker-ws/ # WebSocket relay/client transport adapter
│ ├── security/ # NaCl crypto (X25519, XChaCha20, Ed25519), MLS scaffold
│ ├── mcp-server/ # JSON-RPC MCP stdio server (14 tools)
│ ├── bridge-telegram/ # Telegram bot adapter
│ ├── bridge-a2a/ # A2A protocol bridge (live client round-trip proven; remote agent pending)
│ ├── bridge-openclaw/ # Legacy OpenClaw package, not on the wake/notify path
│ ├── bridge-murmur/ # Murmur-to-Murmur federation (stub)
│ ├── federation/ # org/agent addressing + Ed25519 signed key directory
│ ├── federation-nats/ # fed.* NATS leaf-node/account subject contract
│ └── observability/ # Metrics and tracing (scaffold)
├── scripts/ # Daemon, invite flow, notification setup, demos
├── tests/ # Unit + integration + smoke tests
├── docs/ # ADRs, protocol spec, operations guide
├── deploy/ # systemd unit, docker-compose
├── dashboard/ # Real-time observability web UI + 3D visualization
└── schema/ # JSON schemas for envelope and ACK frames
Design Decisions
| Decision | Choice | Why |
|---|---|---|
| Transport | core NATS + SQLite outbox | Low-latency pub/sub, app-level at-least-once delivery, ACK correlation, DLQ, unbounded dedupe |
| Encryption | X25519 + XChaCha20-Poly1305 | Modern AEAD, NaCl standard, ~30% faster than AES-GCM |
| Signatures | Ed25519 | Fast verification, small keys, deterministic |
| Storage | SQLite (node:sqlite) | Zero dependencies, WAL mode, built into Node 22.13.0+ |
| Group Crypto | MLS (scaffold) | RFC 9420, forward secrecy for groups — deferred to v1.0 |
See ADR-001 and ADR-002 for full rationale.
Native Wake
Murmur wakes agents through native runtime mechanisms instead of tmux or OpenClaw:
- Claude Code:
asyncRewakehook viascripts/wake-drain-claude.sh, or the dependency-free node portscripts/wake-drain-claude.mjs(nosqlite3CLI needed, so it also runs on a default Windows install). - Claude Code cold start:
scripts/wake-drain-claude.mjs --sessionon aSessionStarthook reports messages that arrived while no session was alive. A live session is woken by theStophook; without this one, anything delivered while the contour was dark is never seen. A freshly started session has not taken a turn, soStophas not fired and the poller is not running: the lane is deaf until its first turn (#130). An unattended lane needs one priming turn after launch — the watchdog sends one harmless prompt right after starting it. In tmux, the text andEntermust be two separatesend-keyscalls, or the prompt is never submitted. - Codex CLI: app-server WS-over-UDS
turn/startviascripts/codex-app-server-wake.mjs. - Human notification remains on Telegram/webhook notify queues.
See docs/wake-native.md.
Dashboard
The dashboard is loopback-only and fails closed unless a separate Basic-auth token is present in a private regular file. Create the token once:
install -d -m 0700 ~/.config/murmur
umask 077
openssl rand -hex 32 > ~/.config/murmur/dashboard-token
chmod 0600 ~/.config/murmur/dashboard-token
node dashboard/server.mjs
Open http://127.0.0.1:4280/ and use username murmur with the generated token
as the password. Override the path with DASHBOARD_TOKEN_FILE; do not pass the
token itself in an environment variable or command line.
The dashboard verifies every live envelope signature against configured peer
keys, binds the signed recipient list to the NATS subject, decrypts only traffic
to or from the local agent, and drops unsigned/invalid/cross-party frames. Its
historical feed comes from the daemon’s verified local store. All broker and
database fields are rendered through DOM textContent; the page has no inline
scripts or handlers and is served with a restrictive CSP.
Deployment
Systemd (recommended)
sudo cp deploy/murmur-daemon.service /etc/systemd/system/
sudo systemctl enable --now murmur-daemon
Docker
# Start NATS
docker compose -f deploy/docker-compose.messaging.yml up -d
# Run daemon
node scripts/murmur-daemon.mjs
Kubernetes
Reference manifests for a private in-cluster NATS broker plus one Murmur daemon
live in deploy/kubernetes. They are intended as
a starting point: replace the image name, NATS token, and agent-config.json
secret before applying. The example enables JetStream plus streaming ACK-window
backpressure knobs for durable chunk delivery.
kubectl apply -k deploy/kubernetes
Notification Adapters
# Telegram
node scripts/murmur-notify-init.mjs telegram
# Discord
node scripts/murmur-notify-init.mjs discord
One chat, a thread per peer
A notify target may declare which senders it takes. With a Telegram forum chat that gives every peer its own thread instead of one mixed feed:
"notify": {
"telegram": [
{ "channel": "jarvis", "botToken": "…", "chatId": "-1001234567890", "topicId": 2, "peers": ["agent-jarvis"] },
{ "channel": "sasha", "botToken": "…", "chatId": "-1001234567890", "topicId": 3, "peers": ["agent-sasha"] },
{ "channel": "general", "botToken": "…", "chatId": "-1001234567890", "topicId": 1, "fallback": true }
]
}
peers— the target takes only these agent ids (case-insensitive). An explicit empty list accepts nobody; omitpeersto accept every agent.fallback: true— the target takes what nopeerstarget took, so a peer without a thread of its own still arrives somewhere, without a copy of every message landing there.- Neither field — the target takes everything, which is what every config written before this option does.
If a message matches no target at all, the daemon logs a warning rather than dropping it in silence.
Threads are bound to transport agent IDs (payload.from), not people or channel
members. senderMemberId remains available in the payload but intentionally does
not participate in notification routing. Only peers declares this filter;
there is no from configuration alias. The same rules apply to bare
{botToken, chatId, peers} and {url, peers} configurations and to failed-wake
fallback notifications. Unmatched fallback notifications log the wake failure
reason alongside sender and message ID.
Testing
npm test # Build + all unit tests (57 root tests + workspace suites)
npm run test:integration # ACK correlation integration
npm run test:notify-smoke # Notification adapter smoke
# One-command secure E2E demo
npm run demo:secure
Envelope Format
Every message is an EnvelopeV1:
{
"schemaVersion": "1.0",
"msgId": "uuid",
"conversationId": "dm:alice:bob",
"senderAgentId": "alice",
"recipients": ["bob"],
"createdAt": "2026-04-12T12:00:00.000Z",
"payloadCiphertext": "base64...",
"payloadNonce": "base64...",
"signature": "base64..."
}
Optional fields: ttlSeconds, traceId, sequence, parentMsgId.
See protocol-v1.md for the full specification.
Roadmap
Delivered
Messaging & transport
- [x] E2E encryption — X25519 + XChaCha20-Poly1305 + Ed25519 signatures
- [x] Invite-based peer setup — three commands across two machines, no JSON editing
- [x]
murmur_requestsend-and-wait — wake-accelerated via a read-only ephemeral NATS tap; SQLite store-poll is the durable fallback (daemon stays source of truth for decrypt) - [x] Optional JetStream durability — finite
max_deliver/ack_wait, consumer repair, advisory → DLQ; default-OFF, SQLite outbox stays source of truth; running live on the reference mesh - [x] Dead-letter queue + poison handling · SQLite WAL with optimistic locking
- [x] Exactly-once wake delivery (v2.9) — one durable row per inbound delivery (
delivery_idUNIQUE, committed with its wake state in one transaction), ACK after the durable commit, redelivered envelopes ACKed without a second wake, failed wakes retried under the same id with backoff then dead-lettered visibly, a cursor that never skips a gap and survives restarts, relay replies with a derivedmsgIdso a retry never runs the turn twice - [x] Message streaming — stream frames (start/chunk/end), UTF-8-safe chunking, in-memory + durable SQLite reassembly (out-of-order, idempotent, conflict-reject), backpressure (chunk + byte windows), sha256 integrity, ACK-window
- [x] Agent discovery — presence frames + candidate registry (ttl expiry, dedupe, out-of-order guard), signed presence over NATS (
announcePresence/subscribePresence), operator promote-flow (queryCandidates/promoteCandidate); trust is always an explicit operator promotion — candidates are never auto-trusted
Agent integration & ops
- [x] MCP server with 7 tools — full agent integration
- [x] Native wake (live session) — Claude asyncRewake + Codex app-server UDS, with self-healing thread re-seed (
WakeMonitor) - [x] Codex Desktop exact-task delivery (opt-in) — MCP calls made inside a Desktop task default to
codex:task:; the macOS receive hook uses the sharedcodex queuecommand to inject only into that exact non-archived task. Legacy/unaddressed messages remain inbox-only, and synchronousmurmur_requestreplies are not queued twice. Seedocs/codex-desktop-queue-wake.md. - [x] Scoped channels & session affinity (v2.4) — DB-backed session-ownership leases coordinate actual wake delivery for addressed conversations. Native wake can defer to registered interactive sessions behind
MURMUR_SCOPED_CHANNELS(default-OFF). The MCP logging channel is advisory: it does not claim delivery or register interactive presence, and a logging notification is not an accepted turn. See native wake and advisory-channel behavior. - [x] Phase N / N1-N3 + N6 channel roster, addressing, personalities, MCP — typed
ChannelRosterStorein@murmurv2/core:channelIdis a routing/personality primitive distinct from legacyconversationId, withchannels/channel_membersin a dedicated SQLite store, sharedevaluateAddressing()decisions for reject/append/wake gating, MCP roster tools, and opt-in Codex app-serverthread/startbinding for per-memberpersonaId,model, and base-instruction metadata. - [x] Telegram/Discord/WhatsApp notification adapters
- [x] Observability dashboard (real-time flow + 3D) + Prometheus metrics exporter (outbox depth, delivery latency, error rates)
- [x] Reference deployment — Systemd + Docker, docker-compose (
deploy/docker-compose.messaging.yml) + Kubernetes manifests (deploy/kubernetes/)
Security & protocol
- [x] Auth/authz enforcement mechanism — roster-backed signed tokens (audience/scope/time + signed
subjectactor), optional signedEnvelopeV1.authToken,authorizeInbound(bindssubject === senderAgentId), broker ingress enforcement behindMURMUR_ENFORCE_AUTH(default-OFF, NACKauth-rejected:). Daemon end-to-end wiring → In Progress. - [x] Conformance suite — schema↔runtime-guard agreement matrices for every wire type (envelope, ack, presence, stream); port the fixtures to check a third-party implementation
- [x] Versioned protocol spec — machine-readable schema (
protocol-v1.schema.json) + prose (docs/protocol-v1.md) + compatibility matrix (docs/protocol-compatibility.md)
Distribution
- [x] npm — public under
@murmurv2/*(MIT), in step with release 2.12.0. Registry today:cli2.12.0,core0.6.5,broker-nats0.3.6,broker-ws0.2.3,mcp-server0.2.3,bridge-a2a0.2.1,federation0.2.1,observability0.1.3,security0.1.2,bridge-murmur0.1.2,bridge-openclaw0.1.1,bridge-telegram0.1.1,federation-nats0.1.1
In Progress (next up)
Security first — the shared broker still runs on one token
- [x] Participant enrollment design (#169) — separate client-generated NKeys, one-use expiring invitations, exact subject ACLs and durable revocation tombstones have a validated reference. An isolated real broker test revokes one participant while another keeps its connection. Runtime integration, TLS, JetStream/federation grants and production cutover remain separate; existing invitations still carry the configured shared token.
- [x] Compatible TLS/authentication preparation (#153, 2.12.1 review prerelease) — explicit TLS verification, per-user credentials, guarded migration, restricted JetStream ACLs and a separate read-only dashboard role have isolated tests and documented limits. Existing remote plaintext profiles remain usable.
- [ ] Production TLS and credential cutover — configure the deployment’s certificate and peer roles, coordinate migration/rollback, then verify each live client before retiring shared credentials. The client update does not perform this operational change or implement automatic enrollment/revocation.
- [ ] Auth/authz end-to-end — the mechanism is shipped (
@murmurv2/federation: roster-backed signed tokens,authorizeInbound; broker ingress hookauthorize). Remaining: the daemon does not readMURMUR_ENFORCE_AUTHor build the authorizer from the roster yet, and there is no CLI to mint org-authority tokens. Two small pieces:murmur-daemon.mjswiring (default OFF) andmurmur-auth-token.mjs(mint / verify), then provision tokens to the peers. - [ ] Signed ACK enforcement rollout — inventory actual receiver versions and legacy ACK writers before deploying; verify signed receipts from every required peer. Unsigned frames cannot settle delivery, including explicit legacy configuration. Production rollout remains separate from this code fix.
Delivery & observability
- [ ] Lifecycle events writer —
message_events(queued → delivered → woke → handled → replied),recordEvent,traceMessage,traceConversationandstalledOutboundare in@murmurv2/corewith tests, and nothing in the daemon or MCP server calls them: after four days and thousands of messages the table holds zero rows. Since v2.9 the receiving side is covered by the durablewake_statuson each inbound row; “delivered but never answered” on the outbound side still has no writer. Wire the four events into the daemon (send / broker ACK / wake settle) and the MCP server (send), then surfacestalledOutboundnext tomurmur_inbox. - [ ] Lane coalescing (#124) — since v2.9 inbound messages for one peer/conversation queue in a lane; an opt-in mode to deliver everything queued for a lane as one turn at the turn boundary (quiet window + per-turn cap) is the next step for coordination-heavy days.
- [ ] Phase N tail — N4 chat-session presence (#89), N5 subject scoping (#90).
Needs a real external counterpart (mechanism done, gated on a partner)
- [ ] Federation —
org/agentIdaddressing, Ed25519-signed key directory,fed.*leaf-node/account contract,RosterStore(pinned-key trust + monotonic-version replay guard), and account-config renderer are live-proven in isolation (cross-org sealed+signed delivery on real NATS accounts + leaf-node topology + least-privilege pub/sub). Gate: a second real partner org. The reference mesh’s external peers today share one broker account, so they do not count; the natural first partner is that contour on its own account once #103 lands. - [ ] A2A protocol bridge — a real
@a2a-js/sdkclient → bridge → NATS → reply round-trip is proven (vs a mock internal agent) and Agent-Card discovery is fixed; agent-to-agent over the Murmur mesh is separately proven cross-host (fresh remote agent on published npm, bidirectional encrypt/verify/ACK). Gate: a real remote A2A agent. - [ ] WebSocket transport —
@murmurv2/broker-wsrelay + client are shipped (delivery, ACK correlation, dedupe, invalid-envelope NACKs). Remaining: a browser/edge deployment example and hardening (origin checks, connection limits, TLS behind a proxy). Gate: a real browser or edge consumer — nothing on the reference mesh uses it yet.
Research
- [ ] MLS group encryption (RFC 9420) — forward secrecy for multi-agent groups. Today a feature-flagged scaffold only:
MlsProviderinterface in@murmurv2/securitywith a noop provider that throwsmls-disabled; no OpenMLS backend is wired.
Acknowledgments
This project is built upon the ideas and protocol design of the original Murmur by @slopus. The original Murmur established the core concept of encrypted agent-to-agent messaging with Double Ratchet cryptography. Murmur extends this foundation with core NATS transport, MCP integration, persistent SQLite outbox delivery, and production hardening for autonomous multi-agent workflows.
License
MIT — alexfrmn, 2026
Install
This server does not publish a one-line install command.
Open the repository installation guide New to MCP? Start with the mcpskill.org guide →Configuration
{
"mcpServers": {
"murmur": {
"command": "/absolute/path/to/node",
"args": ["/absolute/path/to/runtime/packages/mcp-server/dist/src/index.js"],
"env": {
"DATA_DIR": "/absolute/path/to/selected-profile",
"MURMUR_STORE_PATH": "/absolute/path/to/selected-profile/murmur.db"
}
}
}
}