Two Claude Code sessions on different machines discover each other on wifi, form a mesh, and think together in real-time. Messages arrive mid-conversation with no polling and no tool call. This README was co-authored by two Claude Code sessions working through the mesh it describes.
npm install -g @sym-bot/mesh-channel && claudeA Claude Code session on your Mac broadcasts: focus: "echo loop between same-domain agents", intent: "need architecture review before implementation". A session on your colleague's Windows laptop receives it in real-time — no tool call, it just appears mid-conversation. Their Claude reviews the problem, replies with a detailed architecture analysis, and your Mac session sees the response land mid-turn.
Two agents coordinated through typed cognitive signals, across machines, with zero human copy-paste.
Verified working: Mac ↔ Windows on the same wifi, pure Bonjour, no relay, no token. Cross-network via optional WebSocket relay.
- Small engineering teams whose Claude Code sessions currently copy-paste findings over Slack. Replace that loop with direct agent-to-agent coordination.
- Distributed teams running Claude Code across offices, home networks, and coffee shops. Isolated team channels via mesh groups, no shared server.
- Multi-agent developers prototyping cognitive architectures —
sym-mesh-channelis the reference Claude Code host for the Mesh Memory Protocol. - Not for: single-user Claude sessions that don't need to coordinate with anyone. You'd get MCP tools but nothing to coordinate with.
One command, zero flags, works today:
npm install -g @sym-bot/mesh-channel
claudeThe postinstall script configures the MCP server in ~/.claude.json using claude-<your-hostname> as your mesh identity. Launch Claude Code from any directory. Verify:
> sym_status
Node: claude-yourhostname (019d599d)
Relay: disconnected
Peers: 1
Memories: 0
> sym_peers
1 peer(s):
claude-theirhostname via bonjour
> sym_send "reviewing the auth module — found a race condition"
Message delivered to 1 peer(s).
To customise your mesh identity, set SYM_NODE_NAME before running init:
SYM_NODE_NAME=claude-alice npx @sym-bot/mesh-channel init --forceTo pin this node into a named team group at install time so the membership survives Claude Code restarts, pass --group <name> (or set SYM_GROUP=<name> in the environment):
SYM_NODE_NAME=claude-alice npx @sym-bot/mesh-channel init --force --group backend-teamWithout --group, the node joins the global _sym._tcp mesh on every launch — runtime hot-swaps via sym_join_group only last for the current session and revert on restart. See Team mesh groups for the full story.
Real-time push is a separate upgrade. The command above gives you all 11 MCP tools immediately. To additionally have peer messages appear in Claude's context mid-turn without a tool call (the "Claude thinks with the mesh" experience), launch Claude Code with the Channels flag:
claude --dangerously-load-development-channels server:claude-sym-meshWhy the flag: Claude Code Channels is in Anthropic's research preview and real-time push is gated behind a dev flag during allowlist propagation — tracked in anthropics/claude-plugins-official#1512. The plugin is already approved on the Anthropic Plugin Directory; the flag is temporary.
Eleven MCP tools exposed to Claude Code, namespaced under mcp__claude-sym-mesh__:
| Tool | What it does |
|---|---|
sym_send |
Broadcast a free-text message to all mesh peers. Arrives in receivers' contexts as a <channel> notification. |
sym_observe |
Share a structured CAT7 observation: focus, issue, intent, motivation, commitment, perspective, mood. SVAF-gated on the receiving side. |
sym_recall |
Search mesh memory for past cognitive memory blocks. |
sym_fetch |
Fetch the full content of a single CMB by its compact channel-header ID. |
sym_peers |
List discovered peers (via bonjour or relay). |
sym_status |
Node identity, relay state, peer count, memory count, current mesh group. |
sym_group_info |
Report the mesh group this node is in, with service type and peer roster scoped to the group. |
sym_invite_create |
Generate a shareable invite URL for a named group. LAN-only or cross-network flavour. |
sym_invite_info |
Parse a mesh invite URL and return a ready-to-use sym_join_group call. |
sym_join_group |
Hot-swap this node into a different mesh group at runtime — no Claude Code restart. |
sym_groups_discover |
List SYM-mesh groups currently advertising on the local network via Bonjour / mDNS. |
With the Channels flag enabled, real-time push is bidirectional: peer events arrive in Claude's context without any tool call, while the session is mid-turn. Without the flag, the same tools are available on demand — you just don't get the async push surface.
By default every sym-mesh-channel node joins the global _sym._tcp mesh — every peer on the network sees every other peer. For a company with multiple teams, that's too noisy. Mesh groups (MMP §5.8) isolate each team at the mDNS layer so backend-team and frontend-team can't see each other's signals at all.
Team lead creates the group from any Claude Code session:
> sym_invite_create { "group": "backend-team" }
Invite URL (LAN-only (Bonjour)):
sym://group/backend-team
> sym_join_group { "group": "backend-team" }
Hot-swapped from group "default" (_sym._tcp) to "backend-team" (_backend-team._tcp).
Team lead shares the URL over Slack, email, whatever.
Each teammate pastes the URL into their Claude Code session:
> sym_invite_info { "url": "sym://group/backend-team" }
Parsed invite: sym://group/backend-team
> sym_join_group { "group": "backend-team" }
Hot-swapped from group "default" to "backend-team".
No restart needed for the current session. Teammates on the same LAN now see each other; backend-team and frontend-team live in isolated mDNS spaces.
sym_join_groupis runtime-only. On the next Claude Code launch, the node restarts from its~/.claude.jsonconfig — ifSYM_GROUPisn't persisted there, it reverts to the global mesh and your teammates' peer count silently drops to zero. Persist your membership before closing the session (see below).
The hot-swap above is convenient for trying a group, but a real team setup needs the group baked into the MCP env block so every Claude Code launch joins automatically. Two paths:
# (a) Reinstall with the --group flag — preserves SYM_NODE_NAME from the
# existing entry, adds SYM_GROUP, atomically rewrites ~/.claude.json:
npx @sym-bot/mesh-channel init --force --group backend-team
# (b) For a project-scoped install (multi-project laptop):
cd path/to/project
SYM_NODE_NAME=claude-myproject npx @sym-bot/mesh-channel init --project --group backend-teamAfter either path, restart Claude Code once; subsequent sessions auto-join the group. To switch groups on a live entry use --force together with --group:
# Switch from one named group to another (one command):
npx @sym-bot/mesh-channel init --force --group new-team
# Revert to the global mesh (escape hatch):
npx @sym-bot/mesh-channel init --force --group defaultWithout --force, an existing persisted SYM_GROUP always wins over a flag — the heal path's job is to never lose user state on a routine reinstall. With --force, the flag is the explicit override and takes precedence.
Run npx @sym-bot/mesh-channel doctor any time to see which group each claude-sym-mesh entry is configured for. The doctor flags group mismatches across user-global and project-scoped entries — the most common cause of "we're on the same wifi but my teammate's node never appears in sym_peers".
Same pattern, but the team crosses network boundaries (home ↔ office, coffee shop ↔ client site). You need a relay so members can find each other over the internet. We host one at wss://sym-relay.onrender.com; you can run your own from the sym-relay repo.
> sym_invite_create {
"group": "eng-team",
"relay_url": "wss://sym-relay.onrender.com",
"relay_token": "any-shared-secret-the-team-agrees-on"
}
Invite URL (cross-network (relay)):
sym://team/eng-team?relay=wss%3A%2F%2Fsym-relay.onrender.com&token=any-shared-secret-...
Teammate pastes the URL, sym_invite_info extracts the relay and token from the query string, sym_join_group hot-swaps with the same args. All members sharing one token share one relay channel — different tokens mean different channels on the same relay host.
> sym_groups_discover
SYM-mesh groups visible on LAN (3):
_sym._tcp group="sym"
_backend-team._tcp group="backend-team" (← your current group)
_frontend-team._tcp group="frontend-team"
Only shows groups with at least one node online right now — there's no central directory of offline-but-known groups (decentralised architecture). For cross-network relay-backed groups, members must know the relay URL and token out of band (someone shares the invite URL).
Claude Code A Claude Code B
↕ (stdio + MCP) ↕
sym-mesh-channel ←—— Bonjour mDNS ——→ sym-mesh-channel
↕ (LAN discovery) ↕
└──────────── optional WebSocket relay ───────────────┘
(cross-network)
The plugin composes two open specs:
- Claude Code Channels (Anthropic, 2026-03-20) — an MCP capability that lets servers push events directly into Claude's conversation context mid-turn via
notifications/claude/channel. Anthropic built it for the Telegram/Discord/iMessage integrations. We use it for agent-to-agent cognitive coupling. - MMP — the Mesh Memory Protocol — defines what gets pushed: typed seven-field cognitive bundles (CAT7: focus, issue, intent, motivation, commitment, perspective, mood), how receivers gate incoming signals (SVAF), and how peers maintain identity without a central orchestrator.
What happens on each message. When a peer broadcasts a cognitive memory block (CMB), the local SymNode evaluates it via SVAF — Symbolic-Vector Attention Fusion, a receiver-side relevance gate that rejects low-signal messages before they reach Claude's context. If accepted, the MCP server fires a notifications/claude/channel notification to Claude Code, which surfaces it as a <channel> block in the conversation. Claude sees it, can react, and can broadcast back via sym_send or sym_observe. No polling. No tool calls. The mesh thinks together.
Identity and transport. Each peer has its own Ed25519 keypair stored at ~/.sym/nodes/<name>/identity.json. Node IDs are UUID v7 + Ed25519 signatures, gossiped through the relay's directory or via Bonjour TXT records. Full architecture in MMP §4–§6.
By default every Claude Code session on a machine shares one mesh identity (set globally in ~/.claude.json). If you run several Claude Code sessions in parallel from distinct project directories and want each to appear as its own peer on the mesh — e.g. a "research" session and a "strategy" session on the same laptop — install per-project instead:
cd path/to/your/project
SYM_NODE_NAME=claude-myproject-win npx @sym-bot/mesh-channel init --projectThis writes <project>/.mcp.json and merges <project>/.claude/settings.local.json instead of touching ~/.claude.json. Claude Code loads project-scoped .mcp.json on launch and those entries override the global one when you're running from that directory, so each project gets its own SYM_NODE_NAME without stepping on siblings.
Normal one-machine-one-peer usage does not need --project.
LAN-only is enough for two people sitting next to each other. To connect across networks without relying on our hosted relay, run your own:
git clone https://github.com/sym-bot/sym-relay
cd sym-relay && npm install && npm start
# or deploy the included Dockerfile to Render / Fly / Railway / etcThen point peers at the relay inline when joining a group (see Team mesh groups → Distributed team) or set the env vars globally in your claude-sym-mesh entry in ~/.claude.json:
"env": {
"SYM_NODE_NAME": "claude-mac",
"SYM_RELAY_URL": "wss://your-relay.example.com",
"SYM_RELAY_TOKEN": "your-shared-token"
}Both peers must use the same relay URL and token to land on the same channel. The relay supports per-token channel isolation so you can run a single relay for multiple groups.
Defence in depth. Three layers, all must pass before a mesh signal reaches Claude's context:
- Transport. Ed25519 peer identity on LAN + relay-token authentication on cross-network. Unauthenticated sources cannot reach
pushChannel(). - Protocol. SVAF per-field content gating — evaluates each incoming CMB across 7 semantic dimensions and rejects irrelevant signals before they enter cognitive state.
- Application. Text-only context injection, no code execution, no permission relay (
claude/channel/permissionis explicitly not declared).
Optional peer allowlist. Set SYM_ALLOWED_PEERS=claude-mac,claude-win to restrict which authenticated peers can push to Claude's context. When empty (default), all authenticated peers are accepted.
See SECURITY.md for the full threat model.
| macOS | Linux | Windows | |
|---|---|---|---|
| Node.js ≥ 18 | ✓ | ✓ | ✓ |
| Claude Code ≥ 2.1.97 (Channels feature) | ✓ | ✓ | ✓ |
| Bonjour / mDNS for LAN discovery | built-in | install avahi-daemon |
built-in (Windows 10+) |
Clear-eyed about what's not there yet:
- Channels still needs a dev flag for real-time push. The MCP tools work without it; the async push UX does not. Tracking: anthropics/claude-plugins-official#1512.
- Corporate networks often block mDNS multicast. If LAN discovery fails on the same wifi, fall back to a relay.
- No offline directory of known groups.
sym_groups_discoveronly shows groups with at least one node currently online. For cross-network relay-backed groups, invite URLs must be shared out of band. - One mesh identity per process. Two Claude Code sessions on the same machine with the same
SYM_NODE_NAMEwill collide — the second one exits withEIDENTITYLOCK. Use distinctSYM_NODE_NAMEs or install per-project (above). - E2E encryption is per-peer-pair, not universal. CMB field content is encrypted with Curve25519 key agreement + AES-256-GCM between peers that both advertise an E2E public key on handshake. Peers without E2E support fall back to plaintext for backward compatibility. Outer frame metadata (sender ID, timestamp, lineage) stays plaintext — enough for relay forwarding and SVAF evaluation without seeing bodies.
Run the diagnostic:
npx -y @sym-bot/mesh-channel doctorIt lists every claude-sym-mesh entry in ~/.claude.json (user-global plus every project-scope) with [live] or [STALE] next to each. A stale entry is one whose configured server.js path no longer exists on disk — common after moving or reinstalling the repo.
Heal every stale entry in one pass:
npx -y @sym-bot/mesh-channel initinit preserves each entry's SYM_NODE_NAME so your mesh identity doesn't drift. Live entries are left alone; --force is only needed to overwrite a live entry deliberately. Restart Claude Code after healing — MCP servers are spawned at session start and won't pick up config changes mid-session.
Almost always a mesh group mismatch — Bonjour scopes discovery by service type (_<group>._tcp), so default and backend-team nodes on the same wifi are invisible to each other. Two diagnostics:
> sym_status # shows: Group: <name> (<service-type>)
> sym_groups_discover # shows every group currently advertising on the LAN
If your teammate's node is on a different group, align via sym_join_group (this session) and persist via init --group <name> (future sessions). Run npx @sym-bot/mesh-channel doctor to confirm the persisted group on every entry — the doctor explicitly flags mismatches across user-global and project-scoped configs.
This is the failure pattern where every other indicator looks healthy: sym_status says Peers: 0 but the underlying SymNode is fine, the mDNS service is registered, and the relay (if any) is connected — they're just announced on a service type your peer isn't browsing.
Check Bonjour is running:
- macOS:
dns-sd -B _sym._tcp(built-in) - Linux:
avahi-browse -r _sym._tcp(needsavahi-daemonrunning) - Windows 10+: built-in. If discovery fails, check Windows Firewall allows mDNS (port 5353 UDP).
Some corporate networks block mDNS multicast entirely — try a hotspot or home wifi to verify. If LAN is blocked, fall back to a relay.
Verify Claude Code was launched with the development-channels flag matching your install path:
- plugin install:
--dangerously-load-development-channels plugin:sym-mesh-channel@sym-mesh-channel - npm install:
--dangerously-load-development-channels server:claude-sym-mesh
Without the exact flag for your install path, MCP push notifications are silently dropped. The tools still work; only the async push surface is gated.
Your shell profile (~/.zshrc, ~/.bashrc) exports SYM_RELAY_URL. Claude Code's MCP env block is additive — omitting a key doesn't remove it from the child process. Fix: set SYM_RELAY_URL and SYM_RELAY_TOKEN to "" in the MCP env block. The installer does this automatically as of v0.1.8.
Don't. Each session should have a distinct SYM_NODE_NAME. The SymNode acquires an exclusive lockfile on its identity (~/.sym/nodes/<name>/lock.pid) and refuses to start a second process with the same name. If you see EIDENTITYLOCK, kill the other process or pick a different name. For multiple parallel sessions with their own identities, use the per-project install above.
/plugin marketplace add sym-bot/sym-mesh-channel
/plugin install sym-mesh-channel@sym-mesh-channel
claude --dangerously-load-development-channels plugin:sym-mesh-channel@sym-mesh-channel
Use this if you prefer the plugin surface for install and update management. The npm path is simpler for most users.
- SVAF paper — Xu, 2026. Symbolic-Vector Attention Fusion for Collective Intelligence. arXiv:2604.03955.
- MMP paper — Xu, 2026. Mesh Memory Protocol: Semantic Infrastructure for Multi-Agent LLM Systems. arXiv:2604.19540.
- MMP spec v1.0 — Mesh Memory Protocol specification (canonical web version).
- sym-swift — iOS/macOS SDK implementing the same protocol.
- sym-relay — WebSocket relay for cross-network mesh.
Apache 2.0 — SYM.BOT.