The OptimClaw Mesh Cluster enables multiple OptimClaw instances to form an autonomous AI mesh network. Each node in the cluster operates independently while collaborating on tasks, sharing workload, and providing fault tolerance. Nodes discover each other automatically via UDP beacons, authenticate using post-quantum cryptography, and coordinate through a gossip-based membership protocol.
Key capabilities:
┌─────────────────────────────────────────────┐
│ Mesh Cluster │
│ │
┌──────────────┐ │ ┌──────────┐ Gossip ┌──────────┐ │
│ Client │──────►│ │ Node A │◄────────────►│ Node B │ │
│ (any chan.) │ │ │ │ (SWIM) │ │ │
└──────────────┘ │ │ ┌──────┐ │ │ ┌──────┐ │ │
│ │ │Agent │ │ │ │Agent │ │ │
│ │ │ Loop │ │ │ │ Loop │ │ │
│ │ └──────┘ │ │ └──────┘ │ │
│ │ ┌──────┐ │ │ ┌──────┐ │ │
│ │ │Tools │ │ │ │Tools │ │ │
│ │ └──────┘ │ │ └──────┘ │ │
│ └─────┬────┘ └────┬─────┘ │
│ │ │ │
│ │ UDP Beacons │ │
│ │◄───────────────────────►│ │
│ │ │ │
│ │ Task Routing │ │
│ │◄───────────────────────►│ │
│ │ (ML-KEM-768 + │ │
│ │ AES-256-GCM) │ │
│ ┌─────┴────┐ ┌────┴─────┐ │
│ │ Node C │◄────────────►│ Node D │ │
│ └──────────┘ Gossip └──────────┘ │
│ │
└─────────────────────────────────────────────┘
Data flow:
1. UDP beacon broadcast → node discovery
2. ML-KEM-768 handshake → shared secret
3. AES-256-GCM encrypted channel established
4. SWIM gossip protocol → membership state
5. Task routing → best node selected via scoring
6. Encrypted task dispatch + result collection
All cluster settings are controlled via environment variables
prefixed with CLUSTER_. They can be set in
~/.optimclaw/.env or passed directly.
| Variable | Type | Default | Description |
|---|---|---|---|
CLUSTER_ENABLED |
bool | false |
Enable mesh cluster mode |
CLUSTER_NODE_ID |
string | auto (hostname) | Unique identifier for this node |
CLUSTER_BIND_ADDR |
string | 0.0.0.0 |
Address to bind the cluster transport |
CLUSTER_BIND_PORT |
u16 | 9400 |
Port for the encrypted cluster transport |
CLUSTER_BEACON_PORT |
u16 | 9401 |
UDP port for discovery beacons |
CLUSTER_BEACON_INTERVAL_MS |
u64 | 5000 |
Milliseconds between beacon broadcasts |
CLUSTER_BEACON_SUBNET |
string | 255.255.255.255 |
Broadcast address for beacons |
CLUSTER_SECRET |
string | required | Pre-shared key for cluster authentication (min 32 chars) |
CLUSTER_SEEDS |
string | "" |
Comma-separated seed node addresses (host:port) for
non-broadcast environments |
CLUSTER_GOSSIP_INTERVAL_MS |
u64 | 1000 |
Milliseconds between gossip protocol rounds |
CLUSTER_GOSSIP_FANOUT |
u8 | 3 |
Number of peers to gossip with per round |
CLUSTER_SUSPICION_MULT |
u8 | 4 |
Multiplier for suspicion timeout (suspicion_mult * gossip_interval) |
CLUSTER_PROBE_INTERVAL_MS |
u64 | 2000 |
Milliseconds between SWIM probe pings |
CLUSTER_PROBE_TIMEOUT_MS |
u64 | 500 |
Timeout for a direct probe response |
CLUSTER_INDIRECT_PROBES |
u8 | 3 |
Number of indirect probes before suspicion |
CLUSTER_TASK_TIMEOUT_SECS |
u64 | 300 |
Timeout for a routed task to complete |
CLUSTER_MAX_NODES |
u16 | 64 |
Maximum cluster size |
CLUSTER_TLS_CERT |
path | "" |
Optional TLS certificate for cross-datacenter transport |
CLUSTER_TLS_KEY |
path | "" |
Optional TLS private key |
CLUSTER_ADVERTISE_ADDR |
string | auto | Address advertised to other nodes (for NAT traversal) |
CLUSTER_ADVERTISE_PORT |
u16 | same as bind | Port advertised to other nodes |
CLUSTER_REGION |
string | "" |
Logical region tag for locality-aware routing |
CLUSTER_CAPABILITIES |
string | "" |
Comma-separated capability tags (e.g.,
gpu,high-memory,docker) |
CLUSTER_ENABLED=true
CLUSTER_SECRET=my-very-long-pre-shared-key-at-least-32-chars
CLUSTER_ENABLED=true
CLUSTER_SECRET=my-very-long-pre-shared-key-at-least-32-chars
CLUSTER_SEEDS=dc1-node1.example.com:9400,dc2-node1.example.com:9400
CLUSTER_ADVERTISE_ADDR=203.0.113.10
CLUSTER_REGION=us-east-1
CLUSTER_TLS_CERT=/etc/optimclaw/cluster.crt
CLUSTER_TLS_KEY=/etc/optimclaw/cluster.key
Nodes discover each other using a UDP beacon protocol. When a node
starts with CLUSTER_ENABLED=true, it begins broadcasting
beacon packets on the configured broadcast address and port.
Offset Size Field
0 4 Magic bytes: 0x4F 0x43 0x4D 0x53 ("OCMS")
4 1 Protocol version (currently 0x01)
5 2 Beacon port (big-endian u16)
7 2 Transport port (big-endian u16)
9 32 Node ID (UTF-8, zero-padded)
41 32 HMAC-SHA256 of bytes 0..41 using CLUSTER_SECRET
Total beacon size: 73 bytes.
CLUSTER_BEACON_INTERVAL_MS milliseconds to
CLUSTER_BEACON_SUBNET:CLUSTER_BEACON_PORT.CLUSTER_SECRET, and extract the sender’s
transport address.CLUSTER_SEEDS to bootstrap. The node will contact seed
addresses directly instead of relying on broadcast.Beacons continue to be sent after joining to help new nodes discover the cluster.
All inter-node communication is encrypted using a hybrid post-quantum scheme to protect against both classical and quantum adversaries.
ML-KEM-768 (formerly CRYSTALS-Kyber) is a lattice-based key encapsulation mechanism standardized in FIPS 203. It provides IND-CCA2 security at NIST security level 3 (roughly equivalent to AES-192).
The handshake proceeds as follows:
CLUSTER_SECRET.CLUSTER_SECRET via
HKDF-SHA256 to produce the final session key, binding the session to the
cluster identity.All messages after the handshake are encrypted with AES-256-GCM using the derived session key:
Session keys are rotated every 1 hour or after 2^32 messages, whichever comes first. Rotation uses a new ML-KEM-768 encapsulation within the existing encrypted channel.
Mesh clusters may carry sensitive task data (credentials, personal information, tool outputs). Harvest-now-decrypt-later attacks make it prudent to deploy post-quantum cryptography today, even before large-scale quantum computers exist.
The cluster uses the SWIM (Scalable Weakly-consistent Infection-style process group Membership) protocol for membership management and failure detection.
Each node maintains a membership list where every entry is in one of three states:
| State | Meaning |
|---|---|
| Alive | Node is healthy and responsive |
| Suspect | Node failed to respond to probes; may be down |
| Dead | Node confirmed unreachable; removed from routing |
Every CLUSTER_GOSSIP_INTERVAL_MS, each node
performs:
CLUSTER_PROBE_TIMEOUT_MS,
send indirect pings through CLUSTER_INDIRECT_PROBES random
members. If still no ack, mark the target as Suspect.CLUSTER_SUSPICION_MULT * CLUSTER_GOSSIP_INTERVAL_MS to
refute by sending an Alive message with a higher incarnation number. If
not refuted, the node transitions to Dead.SWIM provides eventual consistency. After a state change, all nodes converge within O(log N) gossip rounds, where N is the cluster size. With default settings (1s gossip interval, fanout 3), a 64-node cluster converges in under 7 seconds.
When a task arrives at any node, the router decides whether to execute it locally or forward it to a better-suited node. The decision is based on a scoring formula applied to each alive node.
score(node) = w_load * (1 - load_ratio)
+ w_latency * (1 - latency_ratio)
+ w_capability * capability_match
+ w_affinity * affinity_bonus
+ w_locality * locality_bonus
Where:
| Factor | Weight (default) | Description |
|---|---|---|
load_ratio |
w_load = 0.35 |
Current jobs / max parallel jobs (lower is better) |
latency_ratio |
w_latency = 0.25 |
P95 RTT to this node / max observed RTT (lower is better) |
capability_match |
w_capability = 0.25 |
1.0 if node has all required capabilities, 0.0 otherwise |
affinity_bonus |
w_affinity = 0.10 |
1.0 if the task has session affinity to this node, 0.0 otherwise |
locality_bonus |
w_locality = 0.05 |
1.0 if same CLUSTER_REGION, 0.5 if no region set, 0.0
otherwise |
score(node) for all alive nodes including
self.Tasks that reference an ongoing conversation or job context are preferentially routed to the node that holds that context. This avoids expensive context transfer between nodes.
The mesh cluster exposes monitoring endpoints on the standard web gateway.
Returns the cluster status for the local node.
Response:
{
"cluster_enabled": true,
"node_id": "node-alpha",
"state": "alive",
"region": "us-east-1",
"capabilities": ["gpu", "docker"],
"uptime_secs": 86423,
"transport": {
"bind_addr": "0.0.0.0:9400",
"advertise_addr": "203.0.113.10:9400",
"encryption": "ML-KEM-768 + AES-256-GCM",
"protocol_version": 1
},
"membership": {
"alive": 4,
"suspect": 0,
"dead": 1,
"total_seen": 5
},
"routing": {
"local_load": 0.35,
"tasks_routed_out": 142,
"tasks_routed_in": 87,
"tasks_failed_over": 3
}
}Returns the membership list with per-node details.
Response:
{
"nodes": [
{
"node_id": "node-alpha",
"state": "alive",
"addr": "203.0.113.10:9400",
"region": "us-east-1",
"capabilities": ["gpu", "docker"],
"load_ratio": 0.35,
"latency_ms": 0,
"last_seen": "2026-03-29T12:34:56Z",
"incarnation": 7,
"is_self": true
},
{
"node_id": "node-beta",
"state": "alive",
"addr": "203.0.113.11:9400",
"region": "us-east-1",
"capabilities": ["high-memory"],
"load_ratio": 0.12,
"latency_ms": 2,
"last_seen": "2026-03-29T12:34:55Z",
"incarnation": 3,
"is_self": false
}
]
}Puts a node into drain mode (stops accepting new routed tasks, finishes existing ones). Useful before maintenance.
Response:
{
"node_id": "node-beta",
"drained": true,
"remaining_tasks": 2
}Terminal 1 (Node A):
export CLUSTER_ENABLED=true
export CLUSTER_SECRET="change-me-to-a-strong-shared-secret-at-least-32-characters"
export CLUSTER_NODE_ID=node-a
export CLUSTER_BIND_PORT=9400
export CLUSTER_BEACON_PORT=9401
export DATABASE_URL=postgres://localhost/optimclaw_a
optimclaw onboard # if not already configured
cargo runTerminal 2 (Node B):
export CLUSTER_ENABLED=true
export CLUSTER_SECRET="change-me-to-a-strong-shared-secret-at-least-32-characters"
export CLUSTER_NODE_ID=node-b
export CLUSTER_BIND_PORT=9410
export CLUSTER_BEACON_PORT=9401 # same beacon port so they discover each other
export DATABASE_URL=postgres://localhost/optimclaw_b
optimclaw onboard
cargo runWithin 5 seconds, both nodes should discover each other via UDP beacons. Verify by hitting the status endpoint:
curl http://localhost:3000/api/mesh/status | jq .membership
# {"alive": 2, "suspect": 0, "dead": 0, "total_seen": 2}On each machine, set the same CLUSTER_SECRET and
either:
CLUSTER_SEEDS to the address of at least one other
node:export CLUSTER_SEEDS=192.168.1.100:9400The mesh cluster is designed to be secure against:
CLUSTER_SECRET, preventing interception by parties without
the pre-shared key.CLUSTER_SECRET.CLUSTER_SECRET are in the
same trust domain.CLUSTER_SECRET means any attacker can
join the cluster. Rotate the secret and restart all nodes if a
compromise is suspected.| Deployment | Recommendation |
|---|---|
| Same LAN | UDP beacons work out of the box. Use a firewall to restrict beacon and transport ports to trusted hosts. |
| Cross-datacenter | Use CLUSTER_SEEDS, disable beacons by setting
CLUSTER_BEACON_INTERVAL_MS=0, enable TLS
(CLUSTER_TLS_CERT / CLUSTER_TLS_KEY), and
restrict access via network ACLs. |
| Cloud (AWS/GCP/Azure) | Use private VPC networking. Set CLUSTER_ADVERTISE_ADDR
to the private IP. Use security groups to restrict ports 9400-9401. |
CLUSTER_SECRET – Must be
identical on all nodes. Even trailing whitespace matters.CLUSTER_BEACON_PORT.CLUSTER_SEEDS
instead.CLUSTER_BEACON_PORT (UDP) and
CLUSTER_BIND_PORT (TCP) must be open.RUST_LOG=optimclaw::cluster=debug to see beacon
send/receive events.CLUSTER_PROBE_TIMEOUT_MS on busy nodes.CLUSTER_SUSPICION_MULT to give more time
before declaring a node dead.curl localhost:3000/api/mesh/nodes | jq '.nodes[].latency_ms'
to identify slow links.CLUSTER_REGION on
each node so the locality bonus keeps tasks close.CLUSTER_TLS_CERT), verify the certificate
is valid and trusted by the other node.# Check cluster status
curl -s http://localhost:3000/api/mesh/status | jq .
# List all known nodes
curl -s http://localhost:3000/api/mesh/nodes | jq .
# Drain a node before maintenance
curl -s -X POST http://localhost:3000/api/mesh/nodes/node-beta/drain | jq .
# Watch cluster events in real time
RUST_LOG=optimclaw::cluster=debug cargo run 2>&1 | grep cluster