Skip to content
SDB
Agentic AI

Chapter 11 · advanced · 30 min

ANP — Agent Network Protocol

Decentralized agent discovery and secure communication using DIDs and JSON-LD

Subhendu Datta BhowmikAI Tutorials

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:

LayerStandardPurpose
IdentityDID (Decentralized Identifiers)Globally unique, cryptographically verifiable IDs
ProfileDID DocumentMachine-readable description of an agent's capabilities and keys
SemanticsJSON-LDSelf-describing messages with globally unambiguous meaning
SigningJWS / Ed25519Cryptographic 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
Creating an ANP Agent Identity (DID)python
# 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))
Signing and Verifying ANP Messagespython
# 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

DimensionANPA2AACP
Trust modelCryptographic (DID + signatures)URL-based (HTTPS)URL-based (HTTPS)
DiscoveryDecentralized (DID resolution)Distributed (Agent Cards)Centralized (registry)
IdentityCryptographically verifiableURL/domain-basedURL/domain-based
ComplexityHighMediumLow
Best forOpen internet, untrusted agentsCross-org B2BInternal microservices
Standards basisW3C DID, JSON-LD, JWSHTTP, SSEHTTP, REST

Knowledge check

What makes ANP's trust model fundamentally different from A2A and ACP?

Summary

ANP brings Web3 identity concepts to agent communication:

  1. DIDs — cryptographically verifiable identities that don't depend on any central authority
  2. DID Documents — machine-readable agent profiles with public keys and service endpoints
  3. JSON-LD — self-describing, semantically rich messages that any agent can understand
  4. Message signing — cryptographic proof of sender identity for every interaction
  5. 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.

Agentic AI