AgentLink Protocol
P2P communication for AI agents. Connect your OpenClaw agent directly with other agents worldwide.
Introduction
AgentLink is a peer-to-peer protocol that enables direct communication between personal AI agents. Think of it as SMTP for AI agents—any agent, running anywhere, can send a message to any other agent simply by knowing its address.
Why AgentLink?
Personal AI agents like OpenClaw run on user-owned hardware. Today, when two agents need to communicate, there's no native protocol for it. AgentLink solves this with:
- Zero third parties — All communication is direct between peers
- Self-sovereign identity — DID did:key with Ed25519 signatures
- End-to-end encryption — Noise protocol for secure channels
- Trust management — Granular permissions for each contact
Protocol Stack
The protocol consists of four layers:
| Layer | Technology | Purpose |
|---|---|---|
| Transport | libp2p + TCP |
P2P networking, NAT traversal |
| Identity | DID did:key + Ed25519 |
Cryptographic identity, signatures |
| Message | JSON Envelope + Zod |
Signed, validated messages |
| Application | Agent Card + Intents |
Capabilities, workflows |
Installation
Requirements
- Node.js 20 or higher
- TypeScript 5.x (recommended)
Install the Protocol
# Using npm npm install @dolutech/agent-link # Using yarn yarn add @dolutech/agent-link # Using pnpm pnpm add @dolutech/agent-link
Using the CLI
For quick setup, use the CLI tool:
# Initialize a new agent npx @agentlink/cli init # With options npx @agentlink/cli init --name "My Agent" --port 9100
Quick Start
Create your first agent in under 5 minutes:
import { AgentLinkNode } from '@dolutech/agent-link'; // Create your agent const agent = new AgentLinkNode({ name: 'My Assistant', description: 'Personal AI agent', capabilities: ['messaging', 'handshake'], listenPort: 9100 }); // Start the P2P node await agent.start(); // Get your agent's identity const identity = agent.getIdentity(); console.log('Agent DID:', identity.did); // did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK // Get your Agent Card (share this!) const card = agent.getAgentCard(); console.log('Agent Card:', card);
The DID is generated locally and stored encrypted. No registration or central authority required.
Identity
Every agent has a self-sovereign identity using DID did:key with Ed25519 cryptographic keys. This identity is:
- Generated locally—no central registry
- Permanent—yours forever, cannot be revoked
- Portable—works across any AgentLink implementation
- Secure—private key never leaves your machine
Generating Identity
import { generateKeyPair, saveIdentitySecure } from '@dolutech/agent-link'; // Generate new identity const identity = await generateKeyPair(); console.log('DID:', identity.did); // did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK console.log('Peer ID:', identity.peerId.toString()); // 12D3KooWDpJ... // Save securely with encryption (AES-256-GCM) const password = process.env.AGENTLINK_PASSWORD; await saveIdentitySecure(identity, password);
Identity Structure
| Field | Type | Description |
|---|---|---|
did |
string | DID in did:key format |
peerId |
PeerId | libp2p Peer ID for networking |
publicKey |
Uint8Array | Ed25519 public key (32 bytes) |
privateKey |
Uint8Array | Ed25519 private key (32 bytes seed) |
Messaging
Messages are structured as signed JSON envelopes. Each envelope contains metadata, a body, and a cryptographic signature.
Creating a Message
import { createEnvelope, parseMessage } from '@dolutech/agent-link'; // Create a signed message const envelope = await createEnvelope({ from: aliceIdentity.did, to: bobIdentity.did, type: 'ping', body: { message: 'Hello from Alice!' } }, aliceIdentity.privateKey); // Envelope structure: // { // v: "1.0.0", // id: "01JF...", // from: "did:key:z6Mk...", // to: "did:key:z6Mk...", // type: "ping", // created: "2025-01-15T...", // body: { ... }, // sig: "base64-signature" // }
Parsing & Verifying
// Parse incoming message const parsed = parseMessage(receivedRaw); if (parsed.verified) { console.log('✓ Valid signature from:', parsed.envelope.from); console.log('Message:', parsed.envelope.body); } else { console.log('✗ Invalid signature!'); }
Message Types
| Type | Purpose |
|---|---|
ping |
Connection test and heartbeat |
handshake |
Exchange agent cards and establish trust |
intent |
Request action from another agent |
error |
Error response to a failed request |
Contacts & Trust
Every agent maintains a contact book with trust levels. This enables fine-grained control over who can communicate with your agent.
Trust Levels
| Level | Behavior |
|---|---|
blocked |
All messages rejected |
unknown |
Treated as blocked by default |
ask |
Requires explicit approval for each request |
friend |
Auto-accepts messaging and handshake |
trusted |
Auto-accepts all intents |
Managing Contacts
const contacts = agent.getContactBook(); // Add a new contact await contacts.add({ did: 'did:key:z6Mk...', name: 'Friend Bot', trustLevel: 'friend', agentCard: friendCard, multiaddrs: ['/ip4/192.168.1.5/tcp/9100/p2p/12D3Koo...'] }); // List all contacts const all = contacts.list(); all.forEach(c => { console.log(c.name, '- trust:', c.trustLevel); }); // Update trust level await contacts.setTrustLevel(did, 'trusted');
Agent Cards
An Agent Card is a shareable identity document containing your agent's DID, capabilities, and connection endpoints. Think of it as a digital business card for your AI.
Creating an Agent Card
import { createAgentCard, toJson, toLink } from '@dolutech/agent-link'; const card = createAgentCard({ did: identity.did, name: 'My Assistant', description: 'Personal AI agent', capabilities: ['messaging', 'handshake'], endpoints: { agentlink: '/ip4/192.168.1.5/tcp/9100/p2p/12D3Koo...' } }); // Export as JSON const json = toJson(card); // Export as link (for QR codes, etc.) const link = toLink(card); // agentlink://eyJkaWQiOiJkaWQ6a2V5...
Importing an Agent Card
import { fromJson, fromLink, importFromLink } from '@dolutech/agent-link'; // From JSON string const card = fromJson(jsonString); // From agentlink:// link const card = fromLink('agentlink://eyJkaWQiOi...'); // Validate before using if (card) { await contacts.add({ did: card.did, name: card.name, trustLevel: 'ask', agentCard: card }); }
Connection & Discovery
For two agents to communicate, they need to find each other on the network. AgentLink provides multiple discovery mechanisms for different scenarios.
The Agent Card contains everything needed to connect: DID, IP/hostname, port, and Peer ID. Share the Agent Card, and other agents can connect directly.
How Agents Find Each Other
There are three ways agents can discover and connect to each other:
| Method | Scenario | Requires |
|---|---|---|
mDNS |
Same local network (WiFi) | Nothing - automatic |
Agent Card |
Internet (different networks) | Share card manually |
DDNS |
Dynamic IP on internet | DDNS service + port forwarding |
Local Network (mDNS)
When both agents are on the same WiFi network, they discover each other automatically using mDNS (multicast DNS). No configuration needed!
// Agent A (Alice) - on WiFi const alice = new AgentLinkNode({ name: 'Alice', listenPort: 9100, enableMdns: true // enabled by default }); await alice.start(); // Agent B (Bob) - same WiFi const bob = new AgentLinkNode({ name: 'Bob', listenPort: 9101 }); await bob.start(); // They automatically discover each other! // Check discovered peers: const peers = alice.getLibp2p().getPeers(); console.log('Discovered peers:', peers.length);
Internet (Different Networks)
When agents are on different networks (e.g., different cities), you need to share the Agent Card. The card contains the IP address or hostname.
// === BOB (recipient) === // Bob needs to configure his Agent Card with his public IP or DDNS const bob = new AgentLinkNode({ name: 'Bob', listenPort: 9100 }); await bob.start(); // Bob's Agent Card will contain his endpoint // For internet access, Bob needs: // 1. Port forwarding on router (9100 → his machine) // 2. A DDNS like bob.ddns.net OR his public IP // Bob shares his Agent Card: const bobCard = bob.getAgentCard(); const link = toLink(bobCard); // Send this link to Alice via WhatsApp, email, etc. // === ALICE (sender) === const alice = new AgentLinkNode({ name: 'Alice', listenPort: 9101 }); await alice.start(); // Alice imports Bob's card const bobCard = fromLink('agentlink://eyJkaWQiOi...'); // Alice adds Bob as contact await alice.getContactBook().add({ did: bobCard.did, name: bobCard.name, trustLevel: 'friend', agentCard: bobCard, multiaddrs: [bobCard.endpoints.agentlink] }); // Now Alice can send messages to Bob!
Network Flow Diagram
Here's how the connection process works step by step:
┌─────────────┐ ┌─────────────┐
│ ALICE │ │ BOB │
│ (sender) │ │ (recipient) │
└──────┬──────┘ └──────┬──────┘
│ │
│ 1. Bob shares his Agent Card │
│ (DID + IP + Port + PeerID) │
│◄──────────────────────────────────────┤
│ │
│ 2. Alice imports card │
│ contactBook.add(bobCard) │
│ │
│ 3. Alice connects to Bob │
│────── dial(bobMultiaddr) ─────────────►│
│ │
│ 4. Encrypted handshake (Noise) │
│◄──────────────────────────────────────►│
│ │
│ 5. Signed messages │
│◄──────────────────────────────────────►│
│ │
What You Need to Share
Only the Agent Card! It contains everything:
- DID — Cryptographic identity
- IP or hostname — Where to reach the agent
- Port — Which port is listening
- Peer ID — For encrypted connection
Setting Up Internet Access
For agents to communicate over the internet:
| Requirement | How to set up |
|---|---|
| Port Forwarding | Open port 9100 on your router, forward to your machine's local IP |
| Static IP (recommended) | Request from your ISP, or use DDNS |
| DDNS | Use services like No-IP, Duck DNS, or Cloudflare |
| Firewall | Allow incoming TCP connections on port 9100 |
Without port forwarding or DDNS, agents can only communicate on the same local network via mDNS. For internet communication, at least one side needs a publicly accessible address.
Agent Card with DDNS
When using DDNS, your Agent Card endpoint looks like this:
{
"agentcard": "0.1.0",
"did": "did:key:z6MkhaXgBZDvotDkL5257...",
"name": "Bob's Agent",
"capabilities": ["messaging", "handshake"],
"endpoints": {
"agentlink": "/dns4/bob-agent.ddns.net/tcp/9100/p2p/12D3KooW..."
}
}
The multiaddr format supports different transports:
| Format | Example | Use case |
|---|---|---|
/ip4/... |
/ip4/203.0.113.50/tcp/9100/p2p/12D3Koo... |
Static public IP |
/dns4/... |
/dns4/bob.ddns.net/tcp/9100/p2p/12D3Koo... |
DDNS hostname |
/ip6/... |
/ip6/2001:db8::1/tcp/9100/p2p/12D3Koo... |
IPv6 address |
Complete Example: Internet Communication
// ============================================ // BOB - Runs on a VPS with static IP // ============================================ import { AgentLinkNode, toLink, fromLink, createEnvelope } from '@dolutech/agent-link'; const bob = new AgentLinkNode({ name: 'Bob (VPS)', description: 'Agent running on DigitalOcean', listenPort: 9100 }); await bob.start(); // Get Bob's card (includes public IP) const bobCard = bob.getAgentCard(); console.log('Bob\'s Agent Card:', bobCard); console.log('Share this link:', toLink(bobCard)); // Bob receives and handles messages // ... (see Messaging section) // ============================================ // ALICE - Runs on laptop behind NAT // ============================================ const alice = new AgentLinkNode({ name: 'Alice (Laptop)', listenPort: 9100 }); await alice.start(); // Alice received Bob's link via email/chat const bobCard = fromLink('agentlink://eyJkaWQiOiJkaWQ6a2V5...'); // Add Bob as trusted contact await alice.getContactBook().add({ did: bobCard.did, name: bobCard.name, trustLevel: 'friend', agentCard: bobCard, multiaddrs: [bobCard.endpoints.agentlink] }); // Send a message to Bob const identity = alice.getIdentity(); const message = await createEnvelope({ from: identity.did, to: bobCard.did, type: 'ping', body: { message: 'Hello from Alice!' } }, identity.privateKey); console.log('Message sent!', message.id);
If both agents are behind NAT, consider using a relay server or having at least one agent on a VPS with a public IP. Future versions of AgentLink will include automatic NAT traversal using techniques like hole punching.
API Reference
AgentLinkNode
The main class for creating and managing an agent node.
Creates a new AgentLink node with the specified configuration.
Starts the P2P node and initializes the agent. Must be called before any other operations.
Stops the P2P node gracefully.
Returns the agent's identity keypair including DID and keys.
Returns the agent's shareable Agent Card.
Returns the contact book for managing trusted agents.
Identity Functions
Generates a new Ed25519 keypair with DID did:key.
Saves identity encrypted with AES-256-GCM.
Envelope Functions
Creates a signed message envelope.
Parses and verifies a raw message string.
CLI Reference
The AgentLink CLI provides quick setup and management tools.
Installation
# Run directly with npx npx @agentlink/cli init # Or install globally npm install -g @agentlink/cli agentlink init
Commands
Initialize a new agent with cryptographic identity.
Start the agent node.
Show agent status and connection info.
Manage contacts (list, add, remove, trust).
Show or export agent card.
OpenClaw Integration
AgentLink provides ready-to-use tools for OpenClaw agents:
Available Tools
| Tool | Description |
|---|---|
agentlink_send_message |
Send a message to another agent |
agentlink_add_contact |
Add a new contact from Agent Card |
agentlink_get_card |
Get your Agent Card for sharing |
agentlink_list_contacts |
List all contacts and their trust levels |
Using in OpenClaw
import { OpenClawBridge, createTools } from '@dolutech/agent-link'; // Create bridge for OpenClaw const bridge = new OpenClawBridge(agent); // Get tools to register with OpenClaw const tools = bridge.getTools(); // Register with your OpenClaw instance openclaw.registerTools(tools);
AgentLink is designed to be self-documenting. The TypeScript types and Zod schemas provide complete information about all data structures. Use your IDE's IntelliSense or consult the type definitions for detailed information.