Skip to main content

openclaw-p2p

Encrypted agent-to-agent P2P communication. Let your AI agents talk to each other directly — no platform middleman, no message limits, full E2E encryption.

Built for the OpenClaw agent ecosystem, but works with any Python agent framework.

Install

pip install openclaw-p2p

Quick Start

1. Generate your agent's identity

p2p-keygen

Output:

PeerID: p2p:4Tcthedk3w9vSFTmzecPc8vNqqjn
Keys:   ~/.p2p/identity/

Your PeerID is your agent's public address. Share it with other agent owners to connect.

2. Run a relay server (or use someone else's)

Agents behind NAT need a relay to find each other. The relay is a thin WebSocket proxy — it forwards encrypted frames but can't read them (Noise E2E encryption).

p2p-relay --port 8765

That's it. Run this on any machine with a public IP (a $5/month VPS works). The relay:

  • Authenticates agents via Ed25519 signed registration
  • Pins keys on first use (TOFU) to prevent impersonation
  • Rate limits per peer and per IP
  • Sees only ciphertext — zero access to message content

3. Connect two agents

Agent A (your agent):

import asyncio
from p2p import A2ANode, A2AConfig, PeerIdentity, PeerStore, PeerRecord, MessageType

async def main():
    identity = PeerIdentity.load("~/.p2p/identity")
    store = PeerStore("~/.p2p/peers.json")

    # Add agent B as a peer (get their PeerID + relay from their owner)
    # NOTE: relay URL goes in A2AConfig(relay_addresses=...), NOT here!
    # Peer addresses are for direct connections only (e.g. LAN IP:port).
    store.save(PeerRecord(
        peer_id="p2p:THEIR_PEER_ID_HERE",
        alias="AgentB",
        addresses=[],  # empty — connection goes through relay
    ))

    node = A2ANode(
        identity=identity,
        peer_store=store,
        config=A2AConfig(relay_addresses=["ws://relay.example.com:8765"]),
        agent_name="AgentA",
        owner_display_name="Alice",
        introduction="Alice's personal assistant",
        capabilities=["calendar", "web_search", "memory"],
    )

    # Handle incoming messages
    async def on_message(envelope):
        print(f"From {envelope.sender_id}: {envelope.payload}")

    # Handle new peer approval requests
    async def on_approval(peer_id, hello):
        print(f"New peer wants to connect: {hello['agent_name']} ({hello['owner_display_name']})")
        print(f"PeerID: {peer_id}")
        # In production, ask the owner to approve/reject
        await node.approve_handshake(peer_id, trust_level=2)

    node.on_message(on_message)
    node.on_approval_needed(on_approval)
    await node.start()

    # Initiate connection (triggers handshake)
    await node.connect_to_peer("p2p:THEIR_PEER_ID_HERE")

    # Send a message (after handshake completes)
    await node.send("p2p:THEIR_PEER_ID_HERE", MessageType.TEXT, {"text": "Hello from Agent A!"})

    await asyncio.Future()  # run forever

asyncio.run(main())

Agent B does the same in reverse, with Agent A's PeerID and the same relay address.

What happens when two agents connect

1. Both agents connect outbound to the relay (no port forwarding needed)
2. Noise_XX handshake — mutual authentication, forward-secret session keys
3. Identity binding proof — Ed25519 signature over handshake hash (proves identity)
4. HELLO exchange — agent names, capabilities, owner info
5. Challenge-response — Ed25519 nonce signing (defense-in-depth)
6. Owner approval — BOTH owners must explicitly approve (mandatory)
7. ACTIVE — encrypted messaging begins

Reconnections to known peers skip steps 4-6 (just Noise + binding proof).

Message Types

from p2p import MessageType

# Basic messaging
await node.send(peer, MessageType.TEXT, {"text": "Hello!"})

# Task delegation
await node.send(peer, MessageType.TASK_REQUEST, {
    "task": "Review this code",
    "context": "PR #42 in the frontend repo",
})

# Queries
await node.send(peer, MessageType.QUERY, {
    "text": "What's on my calendar tomorrow?",
})

# File transfer (chunked)
await node.send(peer, MessageType.FILE_OFFER, {
    "filename": "report.pdf",
    "size_bytes": 1024000,
    "mime_type": "application/pdf",
    "sha256": "abc123...",
})

All messages are signed (Ed25519) and encrypted (AESGCM via Noise session).

Peer Store

Known peers are stored in ~/.p2p/peers.json:

{
  "p2p:7xK9mQ...3bZ": {
    "peer_id": "p2p:7xK9mQ...3bZ",
    "alias": "Nova",
    "trust_level": 2,
    "description": "Alex's research assistant. Connected 2026-03-20 via relay.",
    "capabilities": ["code", "research"],
    "scope_instructions": "Help with research tasks, don't share my calendar",
    "last_seen": 1742515200.0
  }
}

The description field is for your agent to write notes about the peer. The scope_instructions field controls what your agent should/shouldn't do in conversations with that peer.

Trust Levels

Level Name Meaning
0 Unknown Never connected, not verified
1 Verified Handshake complete, key exchange done
2 Trusted Owner explicitly upgraded trust
3 Allied Fully trusted (e.g., your own agents on different machines)

Trust upgrades are always manual. Agents start at level 1 after the first handshake.

Self-Hosting a Relay

Quick (for testing)

pip install openclaw-p2p
p2p-relay --port 8765

Production (systemd)

Create /etc/systemd/system/p2p-relay.service:

[Unit]
Description=OpenClaw P2P Relay
After=network.target

[Service]
Type=simple
User=p2p
ExecStart=/usr/local/bin/p2p-relay --port 8765
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now p2p-relay

With TLS (recommended for production)

The relay itself runs plain WebSocket. Put it behind a TLS-terminating reverse proxy:

Caddy (automatic HTTPS):

relay.yourdomain.com {
    reverse_proxy localhost:8765
}

nginx:

server {
    listen 443 ssl;
    server_name relay.yourdomain.com;
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:8765;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Then agents connect via wss://relay.yourdomain.com.

Note: Even without TLS, message content is E2E encrypted (Noise protocol). TLS adds protection for metadata (which PeerIDs are connecting, when, how much traffic).

Security Model

Layer What it does
Noise_XX E2E encryption with forward secrecy. Relay sees only ciphertext.
Identity binding Ed25519 signature over Noise handshake hash — proves the PeerID owner is the one in the Noise session
Challenge-response Mutual nonce signing — defense-in-depth on top of Noise
Message signatures Every envelope signed with Ed25519 — verified on receive
TOFU key pinning Relay pins peer_id → public key on first registration
Timestamped registration 60-second freshness window prevents replay of captured registrations
Owner approval Both owners must approve — agents can't autonomously trust each other
Message dedup 10K ring buffer rejects replayed message IDs
Nonce guard Session terminates before AES-GCM nonce can overflow

What the relay can do: see which PeerIDs are connected, when, and message sizes (traffic analysis). What the relay can't do: read messages, forge messages, impersonate agents, or modify traffic without detection.

API Reference

PeerIdentity

identity = PeerIdentity.generate()          # New random identity
identity = PeerIdentity.load(path)          # Load from ~/.p2p/identity/
identity.save(path, passphrase="optional")  # Save (optionally encrypted)
identity.peer_id                            # "p2p:4Tct..."
identity.sign(data) -> bytes                # Ed25519 signature
PeerIdentity.verify(data, sig, pubkey) -> bool

A2ANode

node = A2ANode(identity, peer_store, config,
               agent_name="Name", owner_display_name="Owner",
               capabilities=["list"], introduction="Description")

await node.start()                          # Begin listening + relay registration
await node.stop()                           # Graceful shutdown
await node.send(peer_id, msg_type, payload) # Send message (returns msg ID)
await node.connect_to_peer(peer_id)         # Initiate connection + handshake
await node.approve_handshake(peer_id, trust_level=1, scope_instructions="")
await node.reject_handshake(peer_id)
node.on_message(async_handler)              # Register message callback
node.on_approval_needed(async_handler)      # Register approval callback
node.health()                               # Returns status dict

PeerStore

store = PeerStore("~/.p2p/peers.json")
store.save(PeerRecord(peer_id="p2p:...", alias="Name", addresses=["ws://..."]))
store.get(peer_id) -> PeerRecord | None
store.list_all() -> list[PeerRecord]
store.remove(peer_id)
store.update_trust(peer_id, level)
store.update_description(peer_id, text)

Requirements

  • Python 3.11+
  • Dependencies: pydantic, cryptography, websockets, msgpack (all well-maintained, widely used)

License

MIT

Release files for openclaw-p2p 0.2.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for openclaw-p2p 0.2.5
File Size Uploaded
openclaw_p2p-0.2.5.tar.gz 36.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for openclaw-p2p 0.2.5
File Interpreter ABI Platform
openclaw_p2p-0.2.5-py3-none-any.whl Python 3 none any Details

Total release size: 70.9 kB

Release files / openclaw_p2p-0.2.5.tar.gz

Download URL openclaw_p2p-0.2.5.tar.gz
Size 36.0 kB
Tags Source
SHA-256 checksum
How to use checksums
de66defb4eeb24b9819b45a4d67fd69c3b5cdb66336c260fd3d1b850e6f14431
BLAKE2b-256 checksum
How to use checksums
112e005b21d3b632c2c6fe836a15c976b24feccecf07f4077c5fb3ae693defbb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release files / openclaw_p2p-0.2.5-py3-none-any.whl

Download URL openclaw_p2p-0.2.5-py3-none-any.whl
Size 34.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1fdae7cd2cec1c541a80c611a97d64327eadbf9daedc95e77ae33817ac7d996f
BLAKE2b-256 checksum
How to use checksums
c0631f0cec502ec27db707abf50643f5ffaad1b30f50365510e0b93625174223
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release history Release notifications | RSS feed

This release

0.2.5 This release

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page