npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

libp2p-mesh

v2026.6.28

Published

OpenClaw libp2p P2P mesh network plugin for cross-instance agent communication

Readme

libp2p-mesh

P2P mesh network plugin for OpenClaw. Enables direct peer-to-peer communication between OpenClaw instances using libp2p — no central server required.

Features

  • LAN Discovery — Auto-discovers peers on the same local network via mDNS (Bonjour/Avahi)
  • Direct Messaging — Send messages directly to another peer by its Peer ID
  • Broadcast — Publish messages to a shared topic, flood-fill forwarded across the mesh
  • Bootstrap Entries — Optional static bootstrap peer list for non-LAN scenarios
  • WebSocket Transport — Optional WebSocket support for NAT/firewall-friendly connections
  • NAT Traversal — Built-in AutoNAT + UPnP + Circuit Relay v2 + DCUtR for peers behind home routers / firewalls
  • User Public Attributes — Announce public tags and structured profile attributes so agents can dry-run and send to locally discovered instances by attribute

Requirements

  • OpenClaw >= 2026.3.24
  • Node.js >= 22
  • For LAN discovery: both peers must be on the same local network (same WiFi / Ethernet segment)

Installation

Method 1: Via OpenClaw CLI (Recommended)

openclaw plugins install libp2p-mesh

Method 2: Manual (npm)

如果无法通过 OpenClaw CLI 安装,可以手动安装到 managed npm root:

cd ~/.openclaw/npm
npm install libp2p-mesh

然后刷新插件注册表:

openclaw plugins registry --refresh

The published npm package includes compiled JavaScript under dist/, so OpenClaw and acpx can load it directly.

Automatic setup

After installation, libp2p-mesh automatically:

  • installs or updates its managed prompt block in ~/.openclaw/workspace/AGENTS.md;
  • uses default network settings for mDNS discovery, NAT traversal, DHT, and delivery ACK timeout when those fields are not explicitly configured.

You no longer need to run openclaw libp2p-mesh prompt install or openclaw libp2p-mesh setup for the default setup path. Restart the gateway after installing or updating the plugin:

openclaw gateway restart

Use openclaw libp2p-mesh setup only when you need advanced network settings such as bootstrap nodes, relay nodes, or public announce addresses. Use openclaw libp2p-mesh prompt install only as a manual repair command if the managed AGENTS.md block was removed.

The default generated config shape is:

{
  "plugins": {
    "entries": {
      "libp2p-mesh": {
        "enabled": true,
        "config": {
          "deliveryAckTimeoutMs": 15000
        }
      }
    }
  }
}

安装后配置流程

默认安装路径会自动完成基础配置,不需要手动打开 openclaw.json

1. 安装或更新插件后重启 gateway

openclaw plugins update libp2p-mesh@latest

如果是首次安装,也可以使用:

openclaw plugins install libp2p-mesh

安装或更新后,插件会自动安装或更新 ~/.openclaw/workspace/AGENTS.md 中由 libp2p-mesh 管理的提示词区块,并在未显式配置时使用 mDNS、NAT traversal、DHT 和 delivery ACK timeout 的默认网络设置。

openclaw gateway restart

2. 高级网络或入站目标配置

只有需要高级网络设置或入站投递目标时才运行配置向导:

openclaw libp2p-mesh setup

这个向导会写入 plugins.entries["libp2p-mesh"].config,不会写入 channels["libp2p-mesh"]

向导会把网络配置和入站投递分开处理。网络配置只决定当前节点如何发现或连接其他节点:

  • 使用默认局域网发现。
  • 添加 bootstrap / relay 地址用于跨网络连接。
  • 将当前机器配置为公网 relay 节点。

入站投递配置决定收到 P2P 消息后显示到哪里:

  • 从现有 channels 同步。
  • 手动添加一个 target。
  • 不把 P2P 消息投递到本地 channel。
  • 暂时保持不变。

从现有 channels 同步时,某个 channel 的 target 可以直接留空,表示跳过该 channel。

入站目标示例:

{
  "id": "feishu-main",
  "channel": "feishu",
  "target": "user:ou_xxx"
}

QQ 单聊示例:

{
  "id": "qqbot-main",
  "channel": "qqbot",
  "target": "user:<senderId>"
}

其中 <senderId> 可以从 QQ channel 日志里的 senderId 取得。

3. 手动修复 Agent 提示词

默认安装路径会自动维护提示词区块。只有这个区块被手动删除或需要修复时,才运行:

openclaw libp2p-mesh prompt install

该命令不会覆盖整个 ~/.openclaw/workspace/AGENTS.md 文件,只维护下面两个 marker 之间的区块:

<!-- libp2p-mesh:prompt:start -->
# P2P 中继助手规则
...
<!-- libp2p-mesh:prompt:end -->

如果区块已经存在,命令会询问是否更新到插件内置的最新版。

4. 可选:配置用户公开属性

openclaw libp2p-mesh profile

USER.md 中的公开 tag 由 gateway 使用 OpenClaw 已配置的 agent/API 模型异步提取,不会写回 USER.md。初始 announce 或日志可能先不携带属性;等待后续完整 instance-announce 快照发出后,再按属性发送或排查匹配结果。用户手动新增的结构化属性会保存到:

~/.openclaw/libp2p/user-profile.json

例如:

{
  "kind": "structured",
  "key": "group",
  "value": "实验室",
  "label": "实验室",
  "source": "profile"
}

之后按属性发送时使用 selector:

group=实验室
tag:P2P
#P2P

selector 是发送工具调用时的临时匹配条件,不需要写入配置文件。

5. 可选:配置 announce 日志级别

openclaw libp2p-mesh debug

推荐保持默认 summary。如果排查发现、属性或地址广播问题,可以临时切到 payload 查看完整 announce JSON;排查结束后再切回 summaryoff

6. 可选:配置远端实例本地标签

openclaw libp2p-mesh labels

Labels are private local notes for remote instances you have already discovered. Use them when you want your own grouping to drive sends without asking the remote user to publish that attribute.

7. 重启或启动 gateway

安装、更新或手动修改配置后,重启 gateway 让配置和提示词生效:

openclaw gateway restart

或者停止后重新运行:

openclaw gateway

profile 保存后,如果 gateway 正在运行,会自动刷新并重新广播公开属性;只有 gateway 没有运行时,才需要启动或重启后生效。

启动后可以观察日志:

[libp2p-mesh] Sent instance announce ... attrs=2
[libp2p-mesh] Received instance announce ... changed=true

其中 attrs 是本次 announce 中携带的用户公开属性数量。

Configuration

The default setup path needs no manual configuration. When plugins.entries["libp2p-mesh"].config omits network fields, the plugin uses mDNS discovery, NAT traversal, DHT, and the default delivery ACK timeout automatically.

Use the interactive setup command for advanced network settings and later edits:

openclaw libp2p-mesh setup

The wizard enables the plugin when needed and writes plugins.entries["libp2p-mesh"].config. On later runs, it edits the existing libp2p-mesh entry instead of replacing it blindly. Network discovery is automatic: mDNS, DHT, NAT traversal, relay transport, and hole punching are enabled by default. Network setup only adds optional bootstrap/relay entry addresses or public relay-node parameters; inbound delivery chooses where received P2P messages should appear. After each edit action, the wizard previews the final JSON and only writes after you confirm. When syncing from existing channels, leave a target empty to skip that channel.

The wizard uses OpenClaw's config writer, so the actual file is your normal OpenClaw config path, usually ~/.openclaw/openclaw.json. You do not need to manually edit openclaw.json, and the wizard does not create channels["libp2p-mesh"].

Restart the gateway after applying changes:

openclaw gateway restart

Minimal LAN Setup (Default)

For two computers on the same WiFi or Ethernet segment, no setup wizard is required. The plugin behaves as if the following defaults were present:

{
  "plugins": {
    "entries": {
      "libp2p-mesh": {
        "enabled": true,
        "config": {
          "deliveryAckTimeoutMs": 15000
        }
      }
    }
  }
}

This is sufficient for two computers on the same WiFi to discover each other. The runtime still enables mDNS and DHT automatically even when these fields are not written to openclaw.json.

With Static Port (Optional)

By default, the node picks a random TCP port. To use a fixed port:

{
  "plugins": {
    "entries": {
      "libp2p-mesh": {
        "enabled": true,
        "config": {
          "listenAddrs": ["/ip4/0.0.0.0/tcp/4001"],
          "deliveryAckTimeoutMs": 15000
        }
      }
    }
  }
}

With Bootstrap Nodes (Cross-Network)

If peers are on different networks, run the setup wizard and add bootstrap and optional relay multiaddrs. These addresses are entry points; they do not replace LAN discovery or DHT. The address prompts can be left empty if you do not have an entry node yet.

{
  "plugins": {
    "entries": {
      "libp2p-mesh": {
        "enabled": true,
        "config": {
          "bootstrapList": [
            "/ip4/203.0.113.10/tcp/4001/p2p/12D3KooW..."
          ],
          "relayList": [
            "/ip4/203.0.113.10/tcp/4001/p2p/12D3KooW..."
          ],
          "enableNATTraversal": true,
          "deliveryAckTimeoutMs": 15000
        }
      }
    }
  }
}

Multiple Inbound Targets

Inbound delivery is owned by the receiving OpenClaw instance. In the setup wizard, choose where received P2P messages should appear: sync from existing channels, add a target manually, disable local-channel delivery, or leave the current setting unchanged. When syncing from existing channels, leave a target empty to skip that channel. The sender still sends to the receiver's peer ID or instance ID; the receiver decides which local channels display the incoming message.

Example wizard output with two targets:

{
  "plugins": {
    "entries": {
      "libp2p-mesh": {
        "enabled": true,
        "config": {
          "inboundTargets": [
            {
              "id": "feishu-main",
              "channel": "feishu",
              "target": "user:ou_xxx"
            },
            {
              "id": "telegram-main",
              "channel": "telegram",
              "target": "chat:123456"
            }
          ],
          "deliveryAckTimeoutMs": 15000
        }
      }
    }
  }
}

If inboundTargets is an empty array, inbound delivery is disabled. If inboundTargets is omitted, the plugin keeps any existing inbound behavior unchanged. When inboundTargets is present, it overrides legacy inboundChannel/inboundTarget.

Full Configuration Reference

| Key | Type | Default | Description | |-----|------|---------|-------------| | discovery | string | legacy-compatible | Legacy hint. New setup does not write this field; runtime composes discovery from mDNS, DHT, and optional entry lists. | | listenAddrs | string[] | ["/ip4/0.0.0.0/tcp/0"] | libp2p listen multiaddrs | | bootstrapList | string[] | [] | Optional static bootstrap peer multiaddrs. Non-empty values add bootstrap discovery without disabling mDNS or DHT. | | enableWebSocket | boolean | false | Enable WebSocket transport for browser/NAT compatibility | | meshTopic | string | "openclaw-mesh" | Default broadcast topic | | enableAgentSync | boolean | true | Enable agent state synchronization over the mesh | | enableNATTraversal | boolean | true | Master switch for identify + AutoNAT + UPnP + Circuit Relay v2 + DCUtR | | enableMDNS | boolean | true | Enable mDNS LAN peer discovery | | enableIdentify | boolean | true | libp2p identify protocol (required by AutoNAT and DCUtR) | | enableAutoNAT | boolean | true | AutoNAT — detect whether this node is publicly reachable | | enableUPnP | boolean | true | Attempt UPnP/PMP port mapping on the local gateway | | enableCircuitRelay | boolean | true | Dial peers via /p2p-circuit relay addresses | | enableCircuitRelayServer | boolean | false | Act as a Circuit Relay v2 server (only enable on a public node) | | enableDCUtR | boolean | true | Hole-punching: upgrade a relayed connection to a direct one | | enableDHT | boolean | true | Enable DHT for WAN peer discovery and the pubkey registry | | relayList | string[] | [] | Multiaddrs of relays to reserve a slot on | | discoverRelays | number | 0 | Auto-discover this many relays via content routing | | announceAddrs | string[] | [] | Extra multiaddrs to announce on top of auto-detected ones | | announceLogDetail | "off" \| "summary" \| "payload" | "summary" | Controls instance announce logging. summary logs peer, instance, address count, and attribute count; payload also logs the full announce JSON; off disables only the new announce summary/payload logs and keeps legacy/basic info logs. | | inboundChannel | string | undefined | OpenClaw channel used to display inbound P2P user messages, for example "feishu" | | inboundTarget | string | undefined | OpenClaw channel target for inbound P2P messages, for example user:ou_xxx or chat:oc_xxx | | inboundTargets | array | undefined | Optional list of receiver-owned channel targets for inbound P2P user messages. When present, it overrides inboundChannel/inboundTarget; an empty array disables inbound delivery. | | deliveryAckTimeoutMs | number | 15000 | Timeout for waiting on remote channel delivery ACKs |

Announce Startup and Logging

During gateway startup, libp2p-mesh registers the instance router handlers and direct/broadcast inbound handlers before starting the mesh node. This makes early instance-announce messages observable as soon as peers connect, instead of waiting until after mesh startup has already completed.

Instance announce logs are controlled by plugins.entries["libp2p-mesh"].config.announceLogDetail:

  • summary is the default. It logs send/receive direction, peer ID, instance ID, multiaddr count, and public attribute count. It does not print the full announce JSON.
  • off disables the new announce summary and payload logs. It still keeps legacy/basic info logs such as sent announce lines and instance mapping updates, along with warnings and errors.
  • payload logs the same summary plus the full announce JSON at debug level.

Use the debug command to inspect or change this value:

openclaw libp2p-mesh debug

Full payload logging is intended for short-lived troubleshooting only. Announce payloads can include userPublicAttributes, peer multiaddrs, the instance pubkey, and instance identity fields. After changing the setting, restart the gateway for the new logging level to take effect:

openclaw gateway restart

NAT Traversal

When both peers have a routable address (same LAN, public IPs, or working port-forwarding) no extra setup is needed. The defaults above kick in automatically:

  • UPnP asks your home router to open a port for libp2p TCP.
  • AutoNAT asks peers to verify whether you're reachable from the outside.
  • If you're not directly reachable, Circuit Relay v2 lets another peer (the "relay") forward traffic on your behalf. The relay only sees encrypted bytes — Noise still terminates end-to-end at the original peers.
  • Once a relayed connection is established, DCUtR tries to upgrade it to a direct connection via simultaneous TCP open (hole punching). This works for most home NATs (full-cone, restricted-cone, port-restricted) but not symmetric NATs (CGNAT, some carrier networks).

Behind a NAT — minimal config

You need at least one relay node with a public IP. Set it in relayList:

{
  "plugins": {
    "entries": {
      "libp2p-mesh": {
        "enabled": true,
        "config": {
          "bootstrapList": [
            "/ip4/<RELAY-IP>/tcp/4001/p2p/<RELAY-PEER-ID>"
          ],
          "relayList": [
            "/ip4/<RELAY-IP>/tcp/4001/p2p/<RELAY-PEER-ID>"
          ],
          "enableNATTraversal": true,
          "deliveryAckTimeoutMs": 15000
        }
      }
    }
  }
}

After start-up you should see your node listening on a /p2p-circuit address — that's how remote peers will reach you.

Running your own relay on a public VM

Add enableCircuitRelayServer: true to your config and announce the public address so other peers can dial you:

{
  "plugins": {
    "entries": {
      "libp2p-mesh": {
        "enabled": true,
        "config": {
          "listenAddrs": ["/ip4/0.0.0.0/tcp/4001"],
          "announceAddrs": ["/ip4/<PUBLIC-IP>/tcp/4001"],
          "enableNATTraversal": true,
          "enableCircuitRelayServer": true,
          "deliveryAckTimeoutMs": 15000
        }
      }
    }
  }
}

Detailed walkthrough including how to rent a cloud VM is in ../TESTING_NAT.md.

Usage: Two Computers on the Same LAN

Step 1 — Start both gateways

Computer A (e.g. your desktop):

openclaw gateway run

Computer B (e.g. your laptop or a friend's machine):

openclaw gateway run

Wait ~5–10 seconds for mDNS discovery. You should see in the logs:

[libp2p-mesh] Peer connected: 12D3KooW...

Step 2 — Find your Peer ID

On each computer:

openclaw channels status --probe

Look for the libp2p-mesh channel section — your Peer ID is displayed there. It looks like:

12D3KooWRYyHaWzL8n7i5Z8zZ8Z8Z8Z8Z8Z8Z8Z8Z8Z8Z8

Alternatively, check the gateway startup log:

[libp2p-mesh] Node started. Peer ID: 12D3KooW...

Step 3 — Send a message

From Computer A to Computer B:

openclaw message send libp2p-mesh <COMPUTER-B-PEER-ID> "Hello from A!"

From Computer B to Computer A:

openclaw message send libp2p-mesh <COMPUTER-A-PEER-ID> "Hello from B!"

Step 4 — Verify receipt

Check the gateway logs on the receiving machine. You should see:

[libp2p-mesh] Direct message from <sender-peer-id>: Hello from A!

Sending by OpenClaw Instance ID

When two gateways connect, libp2p-mesh exchanges instance route announcements and automatically writes:

~/.openclaw/libp2p/instance-peer.json

When OPENCLAW_STATE_DIR is set, the file is written to:

$OPENCLAW_STATE_DIR/libp2p/instance-peer.json

Users do not configure this file path. It is plugin-managed state.

For inbound display, run the setup wizard on the receiving instance and sync from configured channels:

openclaw libp2p-mesh setup

The wizard edits plugins.entries["libp2p-mesh"].config and can sync from configured channels, add, edit, remove, or disable inbound delivery targets. Existing inboundTargets are preserved during sync; newly added channels are only prompted once for their target. You do not need to manually edit openclaw.json.

Example result for a single Feishu target:

{
  "plugins": {
    "entries": {
      "libp2p-mesh": {
        "enabled": true,
        "config": {
          "inboundTargets": [
            {
              "id": "feishu-main",
              "channel": "feishu",
              "target": "user:ou_xxx"
            }
          ],
          "deliveryAckTimeoutMs": 15000
        }
      }
    }
  }
}

Multi-channel inbound delivery

The sender still calls p2p_send_instance_message({ "instanceId": "...", "message": "..." }). The receiver chooses where inbound P2P messages appear:

{
  "plugins": {
    "entries": {
      "libp2p-mesh": {
        "enabled": true,
        "config": {
          "inboundTargets": [
            {
              "id": "feishu-main",
              "channel": "feishu",
              "target": "user:ou_xxx"
            },
            {
              "id": "telegram-main",
              "channel": "telegram",
              "target": "chat:123456"
            }
          ],
          "deliveryAckTimeoutMs": 15000
        }
      }
    }
  }
}

If inboundTargets is present, it is used instead of inboundChannel/inboundTarget. The sync action in the wizard only appends missing channels and does not delete or overwrite existing targets. The sender receives per-target delivery status in the tool result.

The OpenClaw agent should prefer:

p2p_send_instance_message({ "instanceId": "<target-instance-id>", "message": "今晚出来吃饭" })

The sender reports success only after the remote OpenClaw instance forwards the message to its configured inbound channel and returns a delivery ACK.

Tools are not configured in openclaw.json; they are registered automatically by the plugin through api.registerTool().

User public attributes

libp2p-mesh can announce user public attributes with instance route announcements. These attributes help agents find matching OpenClaw instances after those instances have already been discovered through the mesh.

There are two public sources:

  • USER.md tags are produced asynchronously by the gateway. The gateway uses the OpenClaw-configured agent/API model to extract tags from USER.md without editing the file.
  • user-profile.json stores manually managed structured attributes such as group, project, role, skill, or a custom key.

The initial base instance-announce may omit userPublicAttributes. After the gateway extracts USER.md tags and merges user-profile.json, it rebroadcasts a full instance-announce snapshot. If extraction is unavailable, USER.md tags are skipped; profile attributes still broadcast.

By default, USER.md is read from:

~/.openclaw/workspace/USER.md

When OPENCLAW_STATE_DIR is set, the plugin reads:

$OPENCLAW_STATE_DIR/workspace/USER.md

Run the profile wizard to manage structured attributes:

openclaw libp2p-mesh profile

The wizard previews read-only USER.md tags and lets you add, edit, or remove only structured profile attributes. Tags extracted from USER.md are not written to user-profile.json; they are merged in memory with profile attributes and broadcast only in full instance announce snapshots after asynchronous extraction completes.

The default profile path is:

~/.openclaw/libp2p/user-profile.json

When OPENCLAW_STATE_DIR is set:

$OPENCLAW_STATE_DIR/libp2p/user-profile.json

Example user-profile.json:

{
  "version": 1,
  "updatedAt": 1782180000000,
  "attributes": [
    {
      "kind": "structured",
      "key": "project",
      "value": "openclaw",
      "label": "project: openclaw",
      "source": "profile"
    },
    {
      "kind": "structured",
      "key": "role",
      "value": "maintainer",
      "label": "role: maintainer",
      "source": "profile"
    }
  ]
}

Remote attributes are cached in plugin-managed instance state under instance-peer.json.userPublicAttributes:

{
  "version": 1,
  "updatedAt": 1782180000000,
  "instances": {
    "[email protected]": {
      "instanceId": "[email protected]",
      "peerId": "12D3KooW...",
      "instanceName": "alice-mac",
      "multiaddrs": ["/ip4/192.168.1.23/tcp/4001"],
      "userPublicAttributes": [
        {
          "kind": "tag",
          "value": "libp2p",
          "label": "libp2p",
          "source": "USER.md"
        },
        {
          "kind": "structured",
          "key": "project",
          "value": "openclaw",
          "label": "project: openclaw",
          "source": "profile"
        }
      ],
      "lastSeenAt": 1782180000000,
      "lastAnnouncedAt": 1782180000000,
      "source": "announce"
    }
  }
}

Local peer labels

Use local peer labels when you want to classify remote instances privately on this machine:

openclaw libp2p-mesh labels

The default labels path is:

~/.openclaw/libp2p/peer-labels.json

When OPENCLAW_STATE_DIR is set:

$OPENCLAW_STATE_DIR/libp2p/peer-labels.json

Example peer-labels.json:

{
  "version": 1,
  "updatedAt": 1782180000000,
  "peers": {
    "[email protected]": {
      "labels": [
        { "key": "group", "value": "实验室" },
        { "key": "project", "value": "openclaw" }
      ]
    }
  }
}

Privacy boundary: peer-labels.json is local state for your gateway. It is not announced to peers, not written into remote instance-peer.json.userPublicAttributes, and not visible to the remote user through the mesh protocol. Public attributes in USER.md and user-profile.json are still broadcast with instance announce messages.

Use p2p_send_user_attribute_message for attribute-based group messages. It defaults to public attributes only, equivalent to scope="public". Always dry-run first. If the dry run matches targets, call the same tool again immediately with the same selector, scope, message, and dryRun: false.

Public scope matches attributes that remote instances announced from their own USER.md or user-profile.json:

p2p_send_user_attribute_message({
  "selector": "project=openclaw",
  "message": "今晚同步一下进展",
  "scope": "public",
  "dryRun": true
})

After a matching dry run:

p2p_send_user_attribute_message({
  "selector": "project=openclaw",
  "message": "今晚同步一下进展",
  "scope": "public",
  "dryRun": false
})

Local scope, written as scope="local" in prompt instructions or "scope": "local" in tool JSON, matches only labels from your peer-labels.json:

p2p_send_user_attribute_message({
  "selector": "group=实验室",
  "message": "我按本地归类发一个提醒",
  "scope": "local",
  "dryRun": true
})

All scope matches both sources and deduplicates by instance:

p2p_send_user_attribute_message({
  "selector": "project=openclaw",
  "message": "公开属性和本地标签都算",
  "scope": "all",
  "dryRun": true
})

Selectors use key=value for structured profile attributes or local labels. Tag matches use tag:value or #value for public USER.md tags. Bare selectors such as 实验室 are rejected because they are ambiguous; use group=实验室 for a structured group or tag:实验室 for a USER.md tag.

p2p_send_user_attribute_message({
  "selector": "#libp2p",
  "message": "libp2p 方向有个问题想确认",
  "dryRun": true
})

The first version matches only instances already present in the local instance-peer.json discovery cache. It does not search the whole network or ask disconnected peers for more users.

Privacy boundary: public attributes are broadcast with instance announce messages to peers your gateway connects to. Do not put private, sensitive, or access-controlled information in USER.md tags or user-profile.json structured attributes.

Troubleshooting

Peers do not discover each other

  1. Confirm same network — Both computers must be on the same subnet (e.g. 192.168.1.x). Check with ip addr or ifconfig.
  2. Check firewall — OpenClaw needs inbound TCP access on the port chosen by libp2p (random by default). Temporarily disable the firewall to test:
    • macOS: sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off
    • Linux (ufw): sudo ufw disable
    • Linux (firewalld): sudo systemctl stop firewalld
  3. Check mDNS — Ensure mDNS/Bonjour/Avahi is running:
    • macOS: built-in, should work
    • Linux: sudo systemctl status avahi-daemon
  4. Use static port + manual IP — If mDNS still fails, add the other node as a bootstrap entry by LAN IP:
    {
      "bootstrapList": [
        "/ip4/192.168.1.42/tcp/4001/p2p/<PEER-ID-OF-OTHER-MACHINE>"
      ]
    }

"Mesh network is not started" error

This error only appears if you run openclaw message send while the gateway is not running. Start the gateway first:

openclaw gateway run

If the gateway is already running, the CLI automatically routes through the gateway (this was fixed in recent versions).

Message timeout after 8 seconds

The peer may be unreachable. Check:

  • Is the target gateway still running?
  • Are both machines on the same network?
  • Is there a firewall blocking the connection?

Connected peers are not visible in logs

Peer connection and disconnection are logged at info level:

[libp2p-mesh] Peer connected: 12D3KooW...
[libp2p-mesh] Instance mapping updated: [email protected] -> 12D3KooW...

If these lines are missing, confirm the gateway is running with normal info logs enabled and that both instances are on the same mDNS, bootstrap, or relay network.

Instance announce routes are missing between two machines

If peers connect but sending by OpenClaw instance ID fails or instance-peer.json is not updated, first confirm both gateways were restarted after the latest config change. On startup, the gateway now attaches the instance router plus inbound message handlers before starting the mesh, so early announces should be handled once the peer connection appears.

For a short debug session on both computers:

  1. Run openclaw libp2p-mesh debug.
  2. Set announceLogDetail to payload and confirm the privacy warning.
  3. Restart both gateways with openclaw gateway restart.
  4. Watch for summary lines and debug lines containing full announce payload JSON.
  5. Return to summary or off with openclaw libp2p-mesh debug, then restart again.

Full payload logs may expose userPublicAttributes, multiaddrs, pubkey, and instance identity, so avoid sharing these logs outside the debugging context.

Architecture

┌─────────────┐      mDNS LAN        ┌─────────────┐
│  Computer A │  ←────────────────→  │  Computer B │
│  (OpenClaw) │    auto-discovery    │  (OpenClaw) │
│             │  ◄─── libp2p/TCP ──► │             │
│  Peer ID: A │                      │  Peer ID: B │
└─────────────┘                      └─────────────┘
  • mDNS broadcasts service announcements on the LAN
  • libp2p handles encrypted peer connections and stream multiplexing
  • Noise encrypts all traffic between peers
  • Messages are deduplicated by message ID to prevent loops

Development

cd extensions/libp2p-mesh

# Standalone mesh test (no OpenClaw required)
node --import tsx test-p2p-communication.mjs

# Build the plugin
cd ../..
pnpm build

License

MIT