The Centralization Problem
A2A and ACP both assume some form of central coordination: A2A agents advertise themselves at a well-known URL (requiring DNS and hosting), and ACP uses a centralized registry. This works within an organization, but it breaks down for truly open, internet-scale agent networks.
What happens when:
- Agents are ephemeral (spun up and destroyed on demand)?
- You want to interact with an agent you've never heard of before?
- You need to cryptographically verify that you're talking to the agent you think you are?
- There's no central authority to trust?
Agent Network Protocol (ANP) addresses these problems using technology from the decentralized identity (DID) ecosystem: cryptographic identifiers, signed messages, and semantic metadata — borrowed from W3C standards and the Web3 identity space.
ANP's Technical Stack
ANP builds on three W3C standards:
| Layer | Standard | Purpose |
|---|---|---|
| Identity | DID (Decentralized Identifiers) | Globally unique, cryptographically verifiable IDs |
| Profile | DID Document | Machine-readable description of an agent's capabilities and keys |
| Semantics | JSON-LD | Self-describing messages with globally unambiguous meaning |
| Signing | JWS / Ed25519 | Cryptographic proof that a message came from a specific agent |
ANP Architecture
Agent A Agent B
(did:web:agent-a.example.com) (did:web:agent-b.example.com)
1. Resolve DID ──────────────────────────────> DID Document
- fetch https://agent-a.example.com/.well-known/did.json
- read public keys and service endpoints
2. Sign message with Agent A's private key
{
"@context": "https://schema.org/",
"@type": "Message",
"sender": "did:web:agent-a.example.com",
"recipient": "did:web:agent-b.example.com",
"content": "Analyze this dataset",
"proof": { "type": "Ed25519Signature2020", ... }
}
3. Send signed message ─────────────────────> Agent B
4. Agent B resolves A's DID, verifies signature
5. Agent B processes message (trusting it's from A)
6. Agent B signs and returns response ────────> Agent A
Why This Matters
- No central registry: Anyone can spin up an ANP agent using just a DID
- Cryptographic trust: You verify the sender's identity mathematically, not by trusting a registry
- Spam/impersonation resistance: Forging messages is computationally infeasible
- Decentralized discovery: Agent metadata lives in the DID Document, resolvable by anyone
# anp_identity.py — Create and publish an ANP agent DID
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey, Ed25519PublicKey
)
from cryptography.hazmat.primitives.serialization import (
Encoding, PublicFormat, PrivateFormat, NoEncryption
)
import base64, json
def generate_agent_keypair():
"""Generate an Ed25519 keypair for the agent."""
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
private_bytes = private_key.private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption())
public_bytes = public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
return {
"private_key_b64": base64.urlsafe_b64encode(private_bytes).decode(),
"public_key_b64": base64.urlsafe_b64encode(public_bytes).decode(),
"key_id": f"key-{base64.urlsafe_b64encode(public_bytes[:8]).decode()}"
}
def create_did_document(domain: str, keypair: dict) -> dict:
"""
Create a DID Document for did:web:<domain>.
Hosted at: https://<domain>/.well-known/did.json
"""
did = f"did:web:{domain}"
key_id = keypair["key_id"]
return {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": did,
"verificationMethod": [
{
"id": f"{did}#{key_id}",
"type": "Ed25519VerificationKey2020",
"controller": did,
"publicKeyMultibase": f"z{keypair['public_key_b64']}"
}
],
"authentication": [f"{did}#{key_id}"],
"assertionMethod": [f"{did}#{key_id}"],
"service": [
{
"id": f"{did}#agent-endpoint",
"type": "ANPAgentEndpoint",
"serviceEndpoint": f"https://{domain}/anp/messages"
},
{
"id": f"{did}#agent-profile",
"type": "ANPAgentProfile",
"serviceEndpoint": f"https://{domain}/anp/profile"
}
]
}
def create_agent_profile(domain: str) -> dict:
"""
JSON-LD agent profile describing capabilities.
Hosted at: https://<domain>/anp/profile
"""
return {
"@context": {
"@vocab": "https://schema.org/",
"anp": "https://agentnetworkprotocol.com/vocab#",
"skill": "anp:skill",
"inputType": "anp:inputType",
"outputType": "anp:outputType"
},
"@type": "anp:Agent",
"name": "Research Intelligence Agent",
"description": "Searches the web and synthesizes research on any topic",
"did": f"did:web:{domain}",
"skill": [
{
"@type": "anp:Skill",
"name": "research",
"description": "Research a topic using web search",
"inputType": "text/plain",
"outputType": ["text/plain", "application/json"]
},
{
"@type": "anp:Skill",
"name": "fact-check",
"description": "Verify claims against authoritative sources",
"inputType": "text/plain",
"outputType": "text/plain"
}
]
}
# Generate and display the identity artifacts
keypair = generate_agent_keypair()
did_doc = create_did_document("research-agent.example.com", keypair)
profile = create_agent_profile("research-agent.example.com")
print("=== Private Key (store securely!) ===")
print(keypair["private_key_b64"])
print("\n=== DID Document (host at /.well-known/did.json) ===")
print(json.dumps(did_doc, indent=2))# anp_messaging.py — Sign and verify ANP messages
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, PrivateFormat, NoEncryption
import base64, json, hashlib, datetime, httpx, asyncio
class ANPAgent:
def __init__(self, did: str, private_key_b64: str, endpoint: str):
self.did = did
self.endpoint = endpoint
private_bytes = base64.urlsafe_b64decode(private_key_b64)
self.private_key = Ed25519PrivateKey.from_private_bytes(private_bytes)
self.public_key = self.private_key.public_key()
def sign_message(self, message: dict) -> dict:
"""Sign a JSON-LD message with the agent's private key."""
# Canonical serialization for signing
canonical = json.dumps(message, sort_keys=True, separators=(",", ":"))
signature_bytes = self.private_key.sign(canonical.encode())
signature_b64 = base64.urlsafe_b64encode(signature_bytes).decode()
return {
**message,
"proof": {
"type": "Ed25519Signature2020",
"created": datetime.datetime.utcnow().isoformat() + "Z",
"verificationMethod": f"{self.did}#key-1",
"proofPurpose": "authentication",
"proofValue": signature_b64
}
}
def create_message(self, recipient_did: str, content: str,
message_type: str = "Request") -> dict:
"""Create a JSON-LD ANP message."""
message = {
"@context": [
"https://schema.org/",
"https://agentnetworkprotocol.com/context/v1"
],
"@type": f"anp:{message_type}",
"id": f"urn:uuid:{hashlib.md5(content.encode()).hexdigest()}",
"sender": self.did,
"recipient": recipient_did,
"created": datetime.datetime.utcnow().isoformat() + "Z",
"content": {
"@type": "anp:TextContent",
"text": content
}
}
return self.sign_message(message)
async def resolve_did(self, did: str) -> dict:
"""Resolve a DID to its DID Document."""
if did.startswith("did:web:"):
domain = did.replace("did:web:", "")
url = f"https://{domain}/.well-known/did.json"
async with httpx.AsyncClient() as client:
resp = await client.get(url)
return resp.json()
raise ValueError(f"Unsupported DID method: {did}")
async def verify_message(self, message: dict) -> bool:
"""Verify a received message's signature using the sender's DID."""
proof = message.get("proof", {})
sender_did = message.get("sender")
if not proof or not sender_did:
return False
# Resolve sender's DID Document
did_doc = await self.resolve_did(sender_did)
# Find the verification method
verification_method_id = proof["verificationMethod"]
public_key_b64 = None
for method in did_doc.get("verificationMethod", []):
if method["id"] == verification_method_id:
# Remove "z" prefix from multibase encoding
public_key_b64 = method["publicKeyMultibase"][1:]
break
if not public_key_b64:
return False
# Reconstruct the original message (without proof)
message_without_proof = {k: v for k, v in message.items() if k != "proof"}
canonical = json.dumps(message_without_proof, sort_keys=True, separators=(",", ":"))
# Verify signature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.exceptions import InvalidSignature
pub_bytes = base64.urlsafe_b64decode(public_key_b64)
public_key = Ed25519PublicKey.from_public_bytes(pub_bytes)
sig_bytes = base64.urlsafe_b64decode(proof["proofValue"])
try:
public_key.verify(sig_bytes, canonical.encode())
return True
except InvalidSignature:
return False
async def send_message(self, recipient_did: str, content: str) -> dict:
"""Create, sign, and send a message to another ANP agent."""
# Resolve recipient's endpoint from DID Document
did_doc = await self.resolve_did(recipient_did)
endpoint = next(
(s["serviceEndpoint"] for s in did_doc.get("service", [])
if s["type"] == "ANPAgentEndpoint"),
None
)
if not endpoint:
raise ValueError(f"No ANP endpoint found for {recipient_did}")
# Create and sign the message
signed_msg = self.create_message(recipient_did, content)
# Send it
async with httpx.AsyncClient() as client:
resp = await client.post(endpoint, json=signed_msg)
return resp.json()ANP Discovery: Finding Agents Without a Registry
In ANP, agent discovery is decentralized. Agents can be found through:
1. Direct DID Resolution
If you know an agent's DID (e.g., did:web:research-agent.acme.com), resolve it directly — no registry needed.
2. Semantic Web Crawling
ANP agent profiles use JSON-LD with schema.org vocabulary. Search engines or specialized crawlers can index them, making agents discoverable like web pages.
3. Community Registries (Optional)
Organizations can publish their ANP agents in voluntary community registries — but these are optional enhancements, not required.
4. Agent Referrals
An agent can include DIDs of other agents it knows about in its profile — enabling organic network growth.
ANP vs A2A vs ACP
| Dimension | ANP | A2A | ACP |
|---|---|---|---|
| Trust model | Cryptographic (DID + signatures) | URL-based (HTTPS) | URL-based (HTTPS) |
| Discovery | Decentralized (DID resolution) | Distributed (Agent Cards) | Centralized (registry) |
| Identity | Cryptographically verifiable | URL/domain-based | URL/domain-based |
| Complexity | High | Medium | Low |
| Best for | Open internet, untrusted agents | Cross-org B2B | Internal microservices |
| Standards basis | W3C DID, JSON-LD, JWS | HTTP, SSE | HTTP, REST |
Knowledge check
What makes ANP's trust model fundamentally different from A2A and ACP?
Summary
ANP brings Web3 identity concepts to agent communication:
- DIDs — cryptographically verifiable identities that don't depend on any central authority
- DID Documents — machine-readable agent profiles with public keys and service endpoints
- JSON-LD — self-describing, semantically rich messages that any agent can understand
- Message signing — cryptographic proof of sender identity for every interaction
- Decentralized discovery — find agents by resolving DIDs, not by querying a registry
In the next chapter, we'll look at UCP (Universal Commerce Protocol) — the emerging standard for agents conducting commerce: making purchases, negotiating contracts, and executing financial transactions autonomously.