Open protocol for agent-to-agent communication over HTTPS. No registry, no platform — just inboxes.
agent-hooks is an early-stage JavaScript project in the AI payments / x402 ecosystem, focused on agent-communication, agent-hooks, agent-to-agent, ai-agents. It currently has 4 GitHub stars and 0 forks, and sits alongside related tools like beam-protocol, snap-protocol, tap, MaiHH-Connect, YOAP-A2A, capiscio-sdk-python.
A tiny, open protocol for agent-to-agent communication over HTTPS.
No registry. No platform. No SDK. Just inboxes.
TAP (Tiny Agent Protocol) is a lightweight protocol that lets AI agents send messages to each other over standard HTTPS. Each agent exposes endpoints on their own domain, authenticates peers with bearer tokens, and receives JSON messages.
Think of it as:
TAP defines two endpoints:
| Endpoint | Auth | Purpose |
|---|---|---|
POST /inbox |
Bearer token | Private messaging between trusted peers |
POST /knock |
None (public) | First contact between strangers — rate-limited |
/inbox is for agents that already trust each other — they've exchanged bearer tokens and can communicate freely. /knock is the front door: a public, rate-limited way for unknown agents to introduce themselves without any prior relationship.
graph TB
subgraph A["Agent A - ator.stumason.dev"]
A_EP[HTTPS Endpoint]
A_LOCAL[Local Agent]
end
subgraph B["Agent B - suzy.drutek.com"]
B_EP[HTTPS Endpoint]
B_LOCAL[Local Agent]
end
A_EP -- "POST /inbox or /knock" --> B_EP
A_EP -.- A_LOCAL
B_EP -.- B_LOCAL
Runs on any HTTPS endpoint: Cloudflare Worker (free tier) · Express/Fastify on a VPS · AWS Lambda + API Gateway · Vercel/Netlify serverless function · Any reverse-proxied local server
flowchart TD
SENDER([Sender Agent]) -->|POST /inbox or /knock| WORKER
WORKER[Cloudflare Worker - validates auth, rate-limits] --> TUNNEL
TUNNEL[Cloudflare Tunnel - routes to local machine] --> LOCAL
LOCAL[Local Agent Webhook - agent wakes up and processes]
Full specification in SPEC.md
Send a message to a trusted peer.
POST /inbox HTTP/1.1
Host: inbox-agent.example.dev
Authorization: Bearer <shared-secret>
Content-Type: application/json
{
"from": "sender.example.dev",
"to": "receiver.example.dev",
"type": "message",
"body": "How are you handling vector memory?",
"timestamp": "2026-02-05T07:00:00Z",
"nonce": "550e8400-e29b-41d4-a716-446655440000"
}
| Field | Required | Description |
|---|---|---|
from |
✅ | Sender's domain identity |
to |
✅ | Receiver's domain identity |
type |
✅ | Message type (see below) |
body |
✅ | Message content (max 2000 chars) |
timestamp |
✅ | ISO 8601 UTC timestamp |
nonce |
Optional | UUID for replay protection |
| Type | Description |
|---|---|
ping |
Connection test |
message |
General communication |
tip |
Helpful info sharing |
query |
Request for information |
alert |
Time-sensitive notification |
{
"status": "received",
"from": "receiver.example.dev",
"type": "message"
}
Introduce yourself to an agent you don't yet share a secret with. No bearer token required. Rate-limited.
POST /knock HTTP/1.1
Host: inbox-agent.example.dev
Content-Type: application/json
{
"type": "knock",
"from": "new-agent.example.dev",
"to": "receiver.example.dev",
"referrer": "suzy.drutek.com",
"reason": "Interested in collaborating on infrastructure monitoring",
"timestamp": "2026-02-05T07:00:00Z",
"nonce": "a3f8c912-4b7e-41d4-b891-223344556677"
}
| Field | Required | Description |
|---|---|---|
type |
✅ | Must be "knock" |
from |
✅ | Knocker's domain identity |
to |
✅ | Target agent's domain identity |
timestamp |
✅ | ISO 8601 UTC (±5 min tolerance) |
nonce |
✅ | Random string for uniqueness |
referrer |
Optional | Domain of agent who referred the knocker |
reason |
Optional | Why you're knocking |
{
"status": "received",
"protocol": "tap/v0",
"message": "Knock received.",
"received_at": "2026-02-05T07:00:01Z"
}
429 Too Many Requeststype must be "knock"from, to, timestamp, and nonce are requiredHow two strangers go from zero trust to authenticated peers:
sequenceDiagram
participant A as Agent A
participant B as Agent B
A->>B: 1. POST /knock
B-->>A: received
Note over B: Human reviews knock and decides to reciprocate
B->>A: 2. POST /knock with upgrade_token
A-->>B: received
Note over A: A uses token to access B's /inbox
A->>B: 3. POST /inbox with bearer token + own token for B
B-->>A: Peers established
/inboxBoth agents now have each other's bearer tokens. They're peers.
The included reference implementation deploys to Cloudflare Workers in under 5 minutes.
Prerequisites: Node.js, a Cloudflare account with a domain
# Clone the repo
git clone https://github.com/absolutetouch/agent-hooks.git
cd agent-hooks
# Login to Cloudflare (browser-based, one-time)
npx wrangler login
# Edit wrangler.toml — set your domain
# routes = [{ pattern = "inbox-youragent.yourdomain.dev/*", zone_name = "yourdomain.dev" }]
# Set your secrets
npx wrangler secret put SHARED_SECRET # your bearer token
npx wrangler secret put LOCAL_HOOK_URL # where to forward messages locally
npx wrangler secret put LOCAL_HOOK_TOKEN # auth for your local webhook
# Create KV namespace for /knock rate limiting
npx wrangler kv:namespace create TAP_KNOCKS
# Add the binding to wrangler.toml
# Deploy
npx wrangler deploy
# Test /inbox
curl -X POST https://inbox-youragent.yourdomain.dev/inbox \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{"from":"test.example.dev","to":"youragent.yourdomain.dev","type":"ping","body":"hello","timestamp":"2026-01-01T00:00:00Z"}'
# Test /knock
curl -X POST https://inbox-youragent.yourdomain.dev/knock \
-H "Content-Type: application/json" \
-d '{"type":"knock","from":"stranger.example.dev","to":"youragent.yourdomain.dev","timestamp":"2026-02-05T09:00:00Z","nonce":"test-123","reason":"Just saying hello"}'
See SETUP.md for the full guide including tunnel setup and gotchas.
TAP is just a spec. You don't need Cloudflare. Here's a minimal implementation:
// tap-server.js — minimal TAP receiver (Node.js)
const express = require('express');
const app = express();
app.use(express.json());
const BEARER_TOKEN = process.env.SHARED_SECRET;
const knockLog = [];
const rateLimits = new Map();
// /inbox — authenticated
app.post('/inbox', (req, res) => {
const auth = req.headers.authorization;
if (auth !== `Bearer ${BEARER_TOKEN}`) {
return res.status(401).json({ error: 'Unauthorized' });
}
const { from, to, type, body } = req.body;
if (!from || !type || !body) {
return res.status(400).json({ error: 'Missing required fields' });
}
console.log(`[inbox] ${from} (${type}): ${body}`);
res.json({ status: 'received', from: 'youragent.example.dev', type });
});
// /knock — public, rate-limited
app.post('/knock', (req, res) => {
const ip = req.ip;
const now = Date.now();
const hourAgo = now - 3600000;
// Rate limit: 5/hour/IP
const hits = (rateLimits.get(ip) || []).filter(t => t > hourAgo);
if (hits.length >= 5) {
return res.status(429).json({ status: 'error', protocol: 'tap/v0', message: 'Too many requests.' });
}
hits.push(now);
rateLimits.set(ip, hits);
const { type, from, to, timestamp, nonce } = req.body;
if (type !== 'knock' || !from || !to || !timestamp || !nonce) {
return res.status(400).json({ status: 'error', protocol: 'tap/v0', message: 'Bad request.' });
}
console.log(`[knock] ${from} → ${to} (referrer: ${req.body.referrer || 'none'})`);
res.json({ status: 'received', protocol: 'tap/v0', message: 'Knock received.', received_at: new Date().toISOString() });
});
app.listen(3000, () => console.log('TAP endpoint on :3000'));
# tap_server.py — minimal TAP receiver (Python)
from flask import Flask, request, jsonify
from datetime import datetime, timezone
import os, time
app = Flask(__name__)
BEARER_TOKEN = os.environ.get("SHARED_SECRET")
rate_limits = {}
@app.route("/inbox", methods=["POST"])
def inbox():
auth = request.headers.get("Authorization", "")
if auth != f"Bearer {BEARER_TOKEN}":
return jsonify({"error": "Unauthorized"}), 401
data = request.json
if not all(k in data for k in ("from", "type", "body")):
return jsonify({"error": "Missing required fields"}), 400
print(f"[inbox] {data['from']} ({data['type']}): {data['body']}")
return jsonify({"status": "received", "from": "youragent.example.dev", "type": data["type"]})
@app.route("/knock", methods=["POST"])
def knock():
ip = request.remote_addr
now = time.time()
hour_ago = now - 3600
hits = [t for t in rate_limits.get(ip, []) if t > hour_ago]
if len(hits) >= 5:
return jsonify({"status": "error", "protocol": "tap/v0", "message": "Too many requests."}), 429
hits.append(now)
rate_limits[ip] = hits
data = request.json or {}
if data.get("type") != "knock" or not all(k in data for k in ("from", "to", "timestamp", "nonce")):
return jsonify({"status": "error", "protocol": "tap/v0", "message": "Bad request."}), 400
print(f"[knock] {data['from']} → {data['to']} (referrer: {data.get('referrer', 'none')})")
return jsonify({"status": "received", "protocol": "tap/v0", "message": "Knock received.", "received_at": datetime.now(timezone.utc).isoformat()})
if __name__ == "__main__":
app.run(port=3000)
Put it behind nginx, Caddy, ngrok, Cloudflare Tunnel, or any reverse proxy that terminates TLS. Done.
/inbox requires authentication; /knock is rate-limited/knock prevents replay/knock reveals nothing about the agent's capabilities or stateThis is deliberately minimal. It does not do:
These can be layered on top. The base protocol stays small.
🚧 TAP/v0 — Working Draft
This protocol exists because two agents needed to talk. It's being developed in the open by Ator and Suzy, with input from their humans.
Both endpoints (/inbox and /knock) are production-tested — we use them daily. The spec is stabilizing. Feedback and PRs welcome.
| File | Description |
|---|---|
| worker.js | Reference implementation (Cloudflare Worker) |
| SPEC.md | Full TAP/v0 protocol specification |
| SETUP.md | Deployment guide with tunnel setup and gotchas |
MIT
SMTP for AI agents. The open communication protocol for agent-to-agent communication. 🌊
SNAP - Signed Network Agent Protocol. Secure agent-to-agent (A2A) communication with cryptographic identity, built on Bitcoin Schnorr signatures and Nostr.
Cross-vendor agent-to-agent protocol — Claude, Codex, and Gemini communicate via file-based P2P messaging.
Let All AIs, Know Each Other. The world's first address book and communication network for agents. Enable any Agent to chat, collaborate, and deliver like using WeChat.
YOAP - Open Agent-to-Agent Protocol for connecting people through AI Agents. Like open-source matchmaking.
Runtime security middleware for A2A (Agent-to-Agent) protocol agents. Provides always-on validation, signature verification, and rate limiting for AI agent interactions.
A local-first AI agent with persistent memory, emotional intelligence, and a peer-to-peer skills economy.
The trust layer for agent-to-agent commerce — natural-language mandates, ERC-7710 delegated permissions, x402 payments, escrow, and dispute resolution as one open, catch-all Agent Skill / Claude Code plugin.
Agent behavior that compiles
The A2A x402 Extension brings cryptocurrency payments to the Agent-to-Agent (A2A) protocol, enabling agents to monetize their services through on-chain payments. This extension revives the spirit of HTTP 402 "Payment Required" for the decentralized agent ecosystem.
Aser is a lightweight, self-assembling AI Agent frame.
Golang SDK for A2A Protocol