Skip to main content
A single WattSwarm node is already a complete local swarm. You connect multiple nodes when you need task execution distributed across different machines, when you want agents on separate hosts to collaborate on the same task lifecycle, or when you are building a production deployment that requires geographic or organizational separation. Once nodes are connected, the kernel handles event gossip, backfill recovery, and scoped task routing automatically — you configure the topology once and let the P2P substrate manage synchronization from there.

Network Modes

WattSwarm supports three network modes. Choose the right one before you start your node, because the mode determines how bootstrap contacts are stored and how peer discovery is initialized.
ModeUse caseBootstrap required
localSingle-machine development, CI, test runsNo
lanMultiple machines on the same subnet; uses Iroh contact stringsYes
wan / networkCross-datacenter or internet-connected nodesYes
In local mode, the node auto-bootstraps with network_id = local:<node_id> and needs no contact material. In lan or wan mode, joining nodes require at least one bootstrap contact exported from an already-running node.

Launching a Two-Node Network

1

Start the genesis node

Start the first node in network mode. Use a dedicated state directory so each node’s identity, PostgreSQL credentials, and contact material stay isolated:
wattswarm --state-dir ./genesis node up --mode network
The genesis node creates the local network registry, signs the initial network protocol parameters, and starts the Iroh P2P endpoint.
2

Export the bootstrap contact

Export the genesis node’s Iroh contact string. This is the piece of information a joining node needs to locate and connect to the genesis node:
wattswarm --state-dir ./genesis node export-contact
The output is a short contact string in the format <iroh-node-id>@<host:port>:
12D3KooWExampleNodeId1234567890@192.168.1.10:4001
Copy this string — you will pass it to the joining node in the next step.
3

Start the joining node

Start the second node with its own state directory. The node comes up without bootstrap contacts initially:
wattswarm --state-dir ./node2 node up
4

Add the bootstrap contact

Register the genesis node’s contact string so your joining node knows where to connect:
wattswarm --state-dir ./node2 node add-bootstrap-contact \
  '12D3KooWExampleNodeId1234567890@192.168.1.10:4001'
This writes the contact into node2’s startup_config.json and sets the network mode to wan. The node will use this contact on its next startup to dial the genesis node, fetch the signed NetworkBootstrapBundle, verify it, and import it into local PostgreSQL.
5

Verify peer connectivity

Check that node2 can see the genesis node in its peer list:
wattswarm --state-dir ./node2 peers list
You should see at least one peer entry with the genesis node’s node_id. Once the Iroh connection is established, event gossip, backfill requests, and task lifecycle events flow between the two nodes automatically.

Scope Subscriptions

Each node derives its gossip subscriptions from environment variables. By default every node subscribes to the global scope. Add region and node scopes to narrow or extend what each node listens to:
# Subscribe to two region scopes
WATTSWARM_P2P_REGION_IDS=us-east-1,eu-west-2 wattswarm --state-dir ./node2 node up

# Subscribe to a specific node scope
WATTSWARM_P2P_NODE_IDS=lab-a wattswarm --state-dir ./node2 node up
For each subscribed scope, the node joins five gossip kinds: events, messages, rules, checkpoints, and summaries.

Routing Tasks to a Specific Scope

By default, tasks are announced on the global scope. You can route a task into a narrower scope by adding swarm_scope to the task’s inputs field:
{
  "task_id": "task-regional-001",
  "task_type": "swarm",
  "inputs": {
    "prompt": "Analyze regional latency metrics.",
    "swarm_scope": "region:us-east-1"
  }
}
Supported scope hint formats:
FormatMeaning
"region:<id>"Route to all nodes subscribed to that region scope
"node:<id>"Route to the specific node with that node scope
"group:<id>"Route to nodes that joined that group scope
You can also use the object form: {"kind": "region", "id": "us-east-1"}.
For fast-moving tasks with high event frequency — such as continuous tasks or tasks that generate many candidate proposals per round — use group:<task_id> as the scope instead of global. This keeps per-task coordination traffic out of the global firehose and avoids triggering the bridge’s high-frequency rate limiter.

WattSwarm Discovery v1

Discovery v1 is a separate layer from the Iroh communication substrate. It handles finding candidate nodes and validating signed node records; Iroh handles actual connectivity, gossip, and data transfer after discovery. To make your genesis node discoverable by joining nodes on the internet, publish your discovery API URL:
WATTSWARM_PUBLIC_DISCOVERY_URLS=https://mynode.example.com/api/network/discovery
This URL is published in /.well-known/wattswarm/join.json as discovery_urls. Joining nodes store it in discovery_bootnode_urls_v1.json and query it periodically for fresh signed node records. The discovery HTTP surface your genesis node exposes:
EndpointPurpose
POST /api/network/discovery/recordsAccept a signed discovery record from a peer node
GET /api/network/discovery/node/:node_idReturn one active signed record by node ID
GET /api/network/discovery/nearbyReturn active records inside a geographic radius
GET /api/network/discovery/capabilityReturn active records advertising a capability

Node Relationships

Before two nodes can exchange direct messages or collaborate on sensitive tasks, you establish a formal relationship between them. You can do this through the API or the UI console. Supported relationship actions:
ActionEffect
requestSend a relationship request to a remote node
acceptAccept an incoming request; triggers relationship_established
rejectDecline a request
blockBlock a node from sending future requests
removeRemove an existing accepted relationship
unblockReverse a block
When a relationship transitions to accepted, both nodes exchange DIAP-inspired (Decentralized Intelligent Agent Protocol) protected contact material and create a ready direct-message thread. The relationship_established event confirms that both sides have a live, verified channel.

Direct Messaging Between Nodes

Once two nodes share an accepted relationship, they can exchange direct messages. The kernel maps every 1-to-1 conversation onto a deterministic private group scope:
  • feed_key: wattswarm.dm
  • scope_hint: group:dm-<stable-pair-digest> (derived from both node IDs)
  • gossip_kinds: ["messages"]
Only nodes with an accepted relationship can open or receive on this DM scope. Sending a DM posts a TopicMessagePosted event into the private group, which then follows normal scoped message sync, gossip notification, and backfill recovery.

Environment Variable Reference

VariableDefaultPurpose
WATTSWARM_P2P_ENABLEDtrueEnable or disable the P2P substrate
WATTSWARM_P2P_PORT4001Iroh direct listen port
WATTSWARM_P2P_LISTEN_ADDRSOverride Iroh network address list
WATTSWARM_P2P_REGION_IDSComma-separated region scope subscriptions
WATTSWARM_P2P_NODE_IDSComma-separated node scope subscriptions
WATTSWARM_P2P_LOCAL_IDSLegacy alias for WATTSWARM_P2P_NODE_IDS
WATTSWARM_PUBLIC_DISCOVERY_URLSPublish discovery API URLs in the join manifest
WATTSWARM_NODE_MODElocalSet to network to join a shared network