Documentation

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

bash
# 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:

bash
# 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:

typescript
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);
💡 Tip

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

typescript
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

typescript
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

typescript
// 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

typescript
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

typescript
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

typescript
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.

Key Concept

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!

typescript
// 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.

typescript
// === 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:

text
┌─────────────┐                         ┌─────────────┐
│   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
⚠️ Important

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:

json
{
  "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

typescript
// ============================================
// 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);
💡 Tip for NAT Traversal

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.

class AgentLinkNode { constructor(config: AgentLinkConfig) }

Creates a new AgentLink node with the specified configuration.

name Agent name (required)
description Agent description
capabilities Array of capability strings
listenPort Port for P2P listener (default: 9100)
dataDir Directory for storing identity and data
async start(): Promise<void>

Starts the P2P node and initializes the agent. Must be called before any other operations.

async stop(): Promise<void>

Stops the P2P node gracefully.

getIdentity(): IdentityKeyPair | null

Returns the agent's identity keypair including DID and keys.

getAgentCard(): AgentCard | null

Returns the agent's shareable Agent Card.

getContactBook(): ContactBook

Returns the contact book for managing trusted agents.

Identity Functions

async function generateKeyPair(options?: GenerateKeyPairOptions): Promise<IdentityKeyPair>

Generates a new Ed25519 keypair with DID did:key.

async function saveIdentitySecure(keyPair: IdentityKeyPair, password: string): Promise<void>

Saves identity encrypted with AES-256-GCM.

Envelope Functions

async function createEnvelope(options: CreateMessageOptions, privateKey: Uint8Array): Promise<Envelope>

Creates a signed message envelope.

function parseMessage(raw: string): ParsedMessage

Parses and verifies a raw message string.

CLI Reference

The AgentLink CLI provides quick setup and management tools.

Installation

bash
# Run directly with npx
npx @agentlink/cli init

# Or install globally
npm install -g @agentlink/cli
agentlink init

Commands

init [options]

Initialize a new agent with cryptographic identity.

--name Agent name
--port Listening port (default: 9100)
--secure Enable encrypted storage (default: true)
start [options]

Start the agent node.

status [options]

Show agent status and connection info.

contacts [action] [options]

Manage contacts (list, add, remove, trust).

card [options]

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

typescript
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);
💡 For AI Agents Reading This

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.