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

agent-dispatch

v1.0.0

Published

Agent Dispatch Messaging Protocol (ADMP) - Universal inbox for autonomous agents

Readme

Agent Dispatch Messaging Protocol (ADMP)

Universal inbox for autonomous agents

ADMP provides a standardized messaging infrastructure for AI agents to communicate reliably and securely. Each agent gets an inbox, messages are cryptographically signed, and delivery is deterministic.

Features

  • Agent Registration - Ed25519 keypair generation
  • Inbox Operations - SEND, PULL, ACK, NACK, REPLY
  • Webhook Push Delivery - Real-time message push to webhook URLs
  • Heartbeat - Session liveness with automatic offline detection
  • Message Leasing - At-least-once delivery with visibility timeouts
  • Signature Verification - Ed25519 authentication on all messages
  • Trust Management - Allowlist-based authorization
  • Background Jobs - Automatic lease reclaim and message expiry
  • OpenAPI Documentation - Interactive Swagger UI at /docs
  • Production Ready - Docker, health checks, structured logging

Quick Start

1. Installation

npm install

2. Configuration

Copy .env.example to .env:

cp .env.example .env

Edit .env:

PORT=8080
NODE_ENV=development
HEARTBEAT_INTERVAL_MS=60000
HEARTBEAT_TIMEOUT_MS=300000
MESSAGE_TTL_SEC=86400

# Storage Backend (optional)
STORAGE_BACKEND=memory  # or "mech" for persistent storage

Storage Backend Options

Memory (Default):

  • Fast in-memory storage
  • Data lost on server restart
  • Ideal for development and testing
  • No external dependencies

Mech (Persistent):

  • Cloud-based persistent storage
  • Data persists across restarts
  • Requires Mech credentials (sign up at mechdna.net)
  • ~35x slower than memory (network overhead)
  • Performance optimizations planned (see PERFORMANCE-ROADMAP.md)
# To use Mech storage:
STORAGE_BACKEND=mech
MECH_APP_ID=your_app_id
MECH_API_KEY=your_api_key
MECH_API_SECRET=your_api_secret

3. Run Server

Development:

npm run dev

Production:

npm start

Docker (Quick Start):

# Start with Docker Compose (recommended)
docker-compose up -d

# Or use the build script
./docker-build.sh --run

For detailed Docker deployment instructions, see DOCKER.md

4. Verify

curl http://localhost:8080/health

Response:

{
  "status": "healthy",
  "timestamp": "2025-11-14T10:00:00.000Z",
  "version": "1.0.0"
}

5. API Documentation

Interactive API Docs (Swagger UI):

  • Visit http://localhost:8080/docs in your browser
  • Try out API endpoints directly from the browser
  • View request/response schemas and examples

OpenAPI Specification:

  • JSON: http://localhost:8080/openapi.json
  • YAML: openapi.yaml in project root

6. Run Tests

Run the full test suite locally:

npm test

This uses Node's built-in node:test runner (requires Node.js ≥18) to run integration tests.

Test Coverage:

The test suite includes:

  • ✅ Server boot, health checks, and stats endpoints
  • ✅ Agent registration, heartbeat, and retrieval
  • ✅ Message lifecycle: send → pull → ack → status flows
  • ✅ Signature verification and timestamp validation
  • ✅ Error cases: invalid signatures, expired timestamps, unknown recipients

Test Output:

Successful test run shows:

# tests 8
# pass 8
# fail 0

CI/CD Integration:

For GitHub Actions, add to your workflow:

- name: Install dependencies
  run: npm install

- name: Run tests
  run: npm test

For other CI systems, ensure Node.js ≥18 is available and run:

npm install && npm test

Test Files:

  • src/server.test.js - Integration tests for HTTP API endpoints

API Documentation

Base URL

http://localhost:8080/api

Endpoints

1. Agent Registration

Register a new agent:

POST /api/agents/register

{
  "agent_type": "claude_session",
  "metadata": {
    "project_name": "my-project",
    "working_directory": "/path/to/project"
  },
  "webhook_url": "https://myagent.com/webhook",  // Optional: for push delivery
  "webhook_secret": "secret123"                   // Optional: auto-generated if omitted
}

Response:

{
  "agent_id": "agent://agent-abc123",
  "agent_type": "claude_session",
  "public_key": "base64-encoded-public-key",
  "secret_key": "base64-encoded-secret-key",
  "webhook_url": "https://myagent.com/webhook",
  "webhook_secret": "auto-generated-secret",
  "heartbeat": {
    "last_heartbeat": 1699999999,
    "status": "online",
    "interval_ms": 60000,
    "timeout_ms": 300000
  }
}

⚠️ Save the secret_key and webhook_secret - they're only returned on registration!


2. Heartbeat

Update agent heartbeat:

POST /api/agents/{agentId}/heartbeat

{
  "metadata": {
    "last_file_edited": "src/app.js"
  }
}

Response:

{
  "ok": true,
  "last_heartbeat": 1699999999,
  "timeout_at": 1700000299,
  "status": "online"
}

Heartbeat keeps your agent alive. If no heartbeat for 5 minutes (default), agent status becomes offline and stops receiving messages.


3. Send Message

Send a message to another agent:

POST /api/agents/{recipientAgentId}/messages

{
  "version": "1.0",
  "id": "msg-uuid",
  "type": "task.request",
  "from": "agent://sender-agent",
  "to": "agent://recipient-agent",
  "subject": "run_tests",
  "body": {
    "command": "npm test"
  },
  "timestamp": "2025-11-14T10:00:00Z",
  "ttl_sec": 86400,
  "signature": {
    "alg": "ed25519",
    "kid": "sender-agent",
    "sig": "base64-signature"
  }
}

Response:

{
  "message_id": "msg-uuid",
  "status": "queued"
}

4. Pull Message (with Lease)

Pull oldest message from inbox:

POST /api/agents/{agentId}/inbox/pull

{
  "visibility_timeout": 60
}

Response (if message available):

{
  "message_id": "msg-uuid",
  "envelope": {
    "version": "1.0",
    "from": "agent://sender",
    "to": "agent://recipient",
    "subject": "run_tests",
    "body": { "command": "npm test" },
    ...
  },
  "lease_until": 1700000059,
  "attempts": 1
}

Response (if inbox empty):

204 No Content

The message is "leased" to you for 60 seconds. ACK or NACK before lease expires, otherwise it's auto-requeued.


5. ACK Message

Acknowledge successful processing:

POST /api/agents/{agentId}/messages/{messageId}/ack

{
  "result": {
    "status": "success",
    "output": "Tests passed"
  }
}

Response:

{
  "ok": true
}

Message is removed from inbox after ACK.


6. NACK Message

Reject or extend lease:

POST /api/agents/{agentId}/messages/{messageId}/nack

{
  "requeue": true
}

Or extend lease:

{
  "extend_sec": 30
}

Response:

{
  "ok": true,
  "status": "queued",
  "lease_until": null
}

7. Reply to Message

Send a correlated response:

POST /api/agents/{agentId}/messages/{originalMessageId}/reply

{
  "version": "1.0",
  "type": "task.result",
  "subject": "test_results",
  "body": {
    "status": "passed",
    "duration_ms": 1234
  },
  "timestamp": "2025-11-14T10:05:00Z",
  "signature": {...}
}

Response:

{
  "message_id": "reply-msg-uuid",
  "status": "queued"
}

Reply automatically sets correlation_id to original message ID and sends to original sender.


8. Message Status

Check message delivery status:

GET /api/messages/{messageId}/status

Response:

{
  "id": "msg-uuid",
  "status": "acked",
  "created_at": 1699999999,
  "updated_at": 1700000059,
  "attempts": 1,
  "lease_until": null,
  "acked_at": 1700000059
}

Statuses: queued, leased, acked, failed, expired


9. Inbox Stats

Get inbox statistics:

GET /api/agents/{agentId}/inbox/stats

Response:

{
  "total": 5,
  "queued": 3,
  "leased": 2,
  "acked": 0,
  "failed": 0
}

10. Trust Management

List trusted agents:

GET /api/agents/{agentId}/trusted

Add to trusted list:

POST /api/agents/{agentId}/trusted
{
  "agent_id": "agent://trusted-agent"
}

Remove from trusted list:

DELETE /api/agents/{agentId}/trusted/{trustedAgentId}

11. Webhook Configuration

Configure webhook for push delivery:

POST /api/agents/{agentId}/webhook

{
  "webhook_url": "https://myagent.com/webhook",
  "webhook_secret": "optional-custom-secret"
}

Response:

{
  "agent_id": "agent://agent-abc123",
  "webhook_url": "https://myagent.com/webhook",
  "webhook_secret": "auto-generated-or-custom-secret"
}

Get webhook configuration:

GET /api/agents/{agentId}/webhook

Response:

{
  "webhook_url": "https://myagent.com/webhook",
  "webhook_configured": true
}

Remove webhook:

DELETE /api/agents/{agentId}/webhook

Response:

{
  "message": "Webhook removed",
  "webhook_configured": false
}

💡 When webhook is configured:

  • Messages are pushed immediately to your webhook URL
  • No polling needed
  • Webhook delivery has automatic retry (3 attempts with exponential backoff)
  • If webhook fails, message stays queued for polling (fallback)

12. System Stats

Get server statistics:

GET /api/stats

Response:

{
  "agents": {
    "total": 10,
    "online": 8,
    "offline": 2
  },
  "messages": {
    "total": 50,
    "queued": 10,
    "leased": 5,
    "acked": 30,
    "failed": 3,
    "expired": 2
  }
}

Message Lifecycle

SEND → queued → PULL → leased → ACK → acked (deleted)
                         ↓
                        NACK → queued (retry)
                         ↓
                      (lease expires) → queued (auto-retry)
                         ↓
                      (TTL expires) → expired

Integration Example

Teleportation Integration

1. Session Start - Register Agent

// .claude/hooks/session_start.mjs
const response = await fetch('http://localhost:8080/api/agents/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    agent_id: `agent://session-${SESSION_ID}`,
    agent_type: 'claude_session',
    metadata: {
      project_name: 'my-project',
      working_directory: process.cwd()
    }
  })
});

const { agent_id, secret_key } = await response.json();

// Save secret_key for signing messages
storeCredentials(agent_id, secret_key);

2. Heartbeat Loop

// Start heartbeat every 60 seconds
setInterval(async () => {
  await fetch(`http://localhost:8080/api/agents/${agent_id}/heartbeat`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      metadata: { last_activity: Date.now() }
    })
  });
}, 60000);

3. Poll Inbox

// Poll inbox every 60 seconds
setInterval(async () => {
  const response = await fetch(`http://localhost:8080/api/agents/${agent_id}/inbox/pull`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ visibility_timeout: 60 })
  });

  if (response.status === 204) {
    // No messages
    return;
  }

  const { message_id, envelope } = await response.json();

  // Validate signature
  const valid = verifySignature(envelope, senderPublicKey);
  if (!valid) {
    console.error('Invalid signature');
    return;
  }

  // Process message
  await processMessage(envelope);

  // ACK
  await fetch(`http://localhost:8080/api/agents/${agent_id}/messages/${message_id}/ack`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ result: { status: 'processed' } })
  });
}, 60000);

Webhook Push Delivery

Webhooks provide real-time message push instead of polling.

Benefits

  • Instant delivery - Messages pushed immediately (no polling delay)
  • 📉 Lower latency - Sub-second delivery instead of up to 60s polling interval
  • 🔋 Reduced load - No constant polling requests to server
  • ♻️ Automatic retry - 3 attempts with exponential backoff (1s, 2s, 4s)
  • 🛡️ Fallback to polling - If webhook fails, message stays queued

How It Works

1. Agent registers with webhook_url
   ↓
2. Message sent to agent
   ↓
3. ADMP server immediately POSTs to webhook_url
   ↓
4. If webhook returns 200 OK → Success ✓
   If webhook fails → Retry with backoff
   If all retries fail → Message stays in queue for polling

Example: Webhook Receiver

1. Start webhook receiver:

node examples/webhook-receiver.js
# Listening on http://localhost:3000/webhook

2. Register agent with webhook:

const agent = await fetch('http://localhost:8080/api/agents/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    agent_type: 'my_agent',
    webhook_url: 'http://localhost:3000/webhook',
    webhook_secret: 'my-secret-key'
  })
});

const { webhook_secret } = await agent.json();
// Save webhook_secret to verify incoming webhooks

3. Implement webhook endpoint:

import express from 'express';
import crypto from 'crypto';

const app = express();
app.use(express.json());

app.post('/webhook', async (req, res) => {
  const payload = req.body;

  // Verify signature
  const signature = payload.signature;
  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
  hmac.update(JSON.stringify({ ...payload, signature: undefined }));
  const expected = hmac.digest('hex');

  if (signature !== expected) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Extract message
  const { envelope } = payload;
  console.log('Received message:', envelope.subject);

  // Process message
  await processMessage(envelope);

  // Acknowledge with 200 OK
  res.json({ ok: true });
});

4. Send message (will be pushed immediately):

await fetch('http://localhost:8080/api/agents/agent://my_agent/messages', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    version: '1.0',
    from: 'agent://sender',
    to: 'agent://my_agent',
    subject: 'task',
    body: { command: 'run tests' },
    timestamp: new Date().toISOString(),
    signature: {...}
  })
});

// Webhook will receive message within milliseconds!

Webhook Payload

When a message arrives, ADMP POSTs this payload to your webhook:

{
  "event": "message.received",
  "message_id": "msg-abc123",
  "envelope": {
    "version": "1.0",
    "from": "agent://sender",
    "to": "agent://recipient",
    "subject": "task",
    "body": {...},
    "timestamp": "2025-11-14T10:00:00Z",
    "signature": {...}
  },
  "delivered_at": 1699999999000,
  "signature": "hmac-sha256-signature"  // If webhook_secret configured
}

Webhook Headers

POST /webhook HTTP/1.1
Host: myagent.com
Content-Type: application/json
User-Agent: ADMP-Server/1.0
X-ADMP-Event: message.received
X-ADMP-Message-ID: msg-abc123
X-ADMP-Delivery-Attempt: 1

Retry Behavior

| Attempt | Delay | Total Time | |---------|-------|------------| | 1 | 0s | 0s | | 2 | 1s | 1s | | 3 | 2s | 3s | | Failed | - | Give up |

After 3 failed attempts, message stays queued for polling.

Webhook vs Polling

| Feature | Webhook | Polling | |---------|---------|---------| | Latency | <100ms | Up to 60s | | Server load | Low (push only) | Higher (constant polling) | | Reliability | Requires reachable endpoint | Always works | | Setup | Configure webhook URL | No setup needed | | Fallback | Falls back to polling | N/A |

Best Practices

Do:

  • Return 200 OK quickly (process async if needed)
  • Verify webhook signature
  • Use HTTPS in production
  • Log failed webhook deliveries
  • Keep webhook endpoint highly available

Don't:

  • Block webhook response waiting for long processing
  • Expose webhook without signature verification
  • Use HTTP in production (security risk)
  • Rely solely on webhooks (always support polling fallback)

Examples

Try it:

# Terminal 1: Start ADMP server
npm start

# Terminal 2: Start webhook receiver
node examples/webhook-receiver.js

# Terminal 3: Run webhook example
node examples/webhook-push.js

Deployment

Docker

docker-compose up -d

Environment Variables

See .env.example for all configuration options.

Key settings:

| Variable | Default | Description | |----------|---------|-------------| | PORT | 8080 | Server port | | HEARTBEAT_INTERVAL_MS | 60000 | Recommended heartbeat interval (1 min) | | HEARTBEAT_TIMEOUT_MS | 300000 | Heartbeat timeout (5 min) | | MESSAGE_TTL_SEC | 86400 | Message TTL (24 hours) | | CLEANUP_INTERVAL_MS | 60000 | Background job interval (1 min) | | API_KEY_REQUIRED | false | Enable API key auth | | MASTER_API_KEY | - | Master API key (if auth enabled) |

Production Checklist

  • [ ] Set NODE_ENV=production
  • [ ] Configure MASTER_API_KEY and set API_KEY_REQUIRED=true
  • [ ] Set appropriate CORS_ORIGIN
  • [ ] Monitor /health endpoint
  • [ ] Set up log aggregation (JSON logs via pino)
  • [ ] Configure resource limits (memory, CPU)
  • [ ] Set up HTTPS reverse proxy (nginx, Caddy)

Architecture

┌─────────────────┐         ┌──────────────────┐
│  Agent A        │────────▶│   ADMP Server    │
│  (Sender)       │         │   (Relay/Hub)    │
└─────────────────┘         └──────────────────┘
                                     │
                                     ▼
                            ┌──────────────────┐
                            │  Agent B Inbox   │
                            │  [msg1, msg2,..] │
                            └──────────────────┘
                                     │
                                     ▼
┌─────────────────┐         ┌──────────────────┐
│  Agent B        │◀────────│   PULL (lease)   │
│  (Receiver)     │         │   Process        │
└─────────────────┘         │   ACK            │
                            └──────────────────┘

Security

Message Signing

All messages should be signed with Ed25519:

import nacl from 'tweetnacl';

// Create signing base
const base = `${timestamp}\n${bodyHash}\n${from}\n${to}\n${correlationId}`;

// Sign
const signature = nacl.sign.detached(
  Buffer.from(base),
  secretKey
);

envelope.signature = {
  alg: 'ed25519',
  kid: 'agent-id',
  sig: Buffer.from(signature).toString('base64')
};

Signature Verification

Server verifies signatures on SEND:

import { verifySignature } from './utils/crypto.js';

const valid = verifySignature(envelope, senderPublicKey);
if (!valid) {
  throw new Error('Invalid signature');
}

Replay Protection

  • Timestamp validation: ±5 minutes window
  • TTL enforcement
  • Lease-based processing prevents duplicate processing

License

MIT

Contributing

See CLAUDE.md for development workflow and methodology.

Support

  • Documentation: See /whitepaper/v1.md for full ADMP specification
  • Issues: GitHub Issues
  • Email: [email protected]