A2A Agent Cards Over Pilot Tunnels

A2A Agent Cards Over Pilot Tunnels

Google's Agent-to-Agent (A2A) protocol is the first serious attempt at standardizing how AI agents communicate at the application layer. It defines Agent Cards for capability advertisement, a task lifecycle with streaming support, and JSON-RPC 2.0 as the wire format. It answers the question: what do agents say to each other?

What it does not answer is: how do they reach each other?

A2A assumes HTTP. Agent Cards are served at /.well-known/agent-card.json. Task requests are POST payloads. Streaming uses Server-Sent Events. This works when both agents have publicly routable URLs. It breaks when either agent is behind NAT, inside a container, on a laptop, or anywhere without a stable domain name. And that describes a lot of real agent deployments.

Pilot Protocol solves exactly this gap. It provides the network layer -- virtual addressing, NAT traversal, encrypted tunnels, peer discovery, and trust management -- that A2A's application layer needs but does not provide. This article walks through the integration: A2A for semantics, Pilot for transport.

Two Layers, One Stack

The A2A and Pilot protocols operate at different layers of the stack, which is precisely why they compose so well:

ConcernA2APilot
Capability advertisementAgent Cards (JSON)Registry (hostname + up to 3 tags)
Task lifecyclesend/get/cancel task (JSON-RPC)Messaging ports (send/recv)
Wire formatJSON-RPC 2.034-byte binary header + payload
TransportHTTP/HTTPSUDP overlay with sliding window
DiscoveryWell-known URL or directoryRegistry resolve + trust handshake
SecurityTLS + auth headersX25519 + AES-256-GCM, encrypt-by-default
NAT traversalNot addressedSTUN, hole-punch, relay (3-tier)
Trust modelOAuth / API keysPrivate-by-default, trust-gated resolve

A2A handles what agents say. Pilot handles how they reach each other. When you run A2A over Pilot, you get the rich application semantics of Agent Cards and JSON-RPC tasks, delivered over encrypted tunnels that traverse NAT, with discovery that does not require DNS or public URLs.

The key insight: A2A defines the language of agent collaboration. Pilot provides the phone system. You need both to have a conversation.

Agent Cards Meet the Pilot Registry

An A2A Agent Card is a JSON document that describes what an agent can do. It lists the agent's name, description, supported skills, input/output content types, and the URL where tasks can be submitted. Here is a minimal Agent Card:

{
  "name": "ResearchAgent",
  "description": "Summarizes academic papers on any topic",
  "url": "https://research-agent.example.com",
  "version": "1.0.0",
  "capabilities": {
    "streaming": true,
    "pushNotifications": false
  },
  "skills": [
    {
      "id": "summarize-papers",
      "name": "Paper Summarization",
      "description": "Find and summarize papers on a given topic",
      "tags": ["research", "nlp", "summarization"],
      "examples": ["Summarize recent papers on transformer attention"]
    }
  ],
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain", "text/markdown"]
}

The problem is the url field. In standard A2A, this must be a publicly reachable HTTPS endpoint. If your agent runs on a laptop behind a home router, there is no URL to put there.

With Pilot, the url becomes a Pilot address. Instead of https://research-agent.example.com, the agent is reachable at its 48-bit virtual address, say 1:0000.0042.00A1, on port 80 (Pilot's HTTP port). The Agent Card is served by a lightweight HTTP server running over the Pilot tunnel. Any peer that can resolve the agent's address can fetch the card and submit tasks.

Pilot's registry stores each agent's hostname and up to three tags. A peer that knows an agent's hostname resolves it with pilotctl find <hostname>; a private agent resolves only for peers it trusts or shares a network with. There is no search by tag or capability, so you learn hostnames out of band or from a network's member list. The A2A Agent Card adds the semantic detail -- skill descriptions, examples, content types -- on top of that. The two systems reinforce each other: Pilot tells you whether an agent exists and is reachable; the Agent Card tells you what its tasks should look like.

Serving Agent Cards Over Pilot HTTP

Pilot provides an HTTP port (port 80) that tunnels standard HTTP traffic over the encrypted overlay. Running an A2A-compliant HTTP server on this port is straightforward. Here is a Go implementation that serves an Agent Card and handles A2A JSON-RPC task requests:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"

    "github.com/pilot-protocol/common/driver"
)

// AgentCard represents an A2A Agent Card
type AgentCard struct {
    Name         string       `json:"name"`
    Description  string       `json:"description"`
    URL          string       `json:"url"`
    Version      string       `json:"version"`
    Capabilities Capabilities `json:"capabilities"`
    Skills       []Skill      `json:"skills"`
}

type Capabilities struct {
    Streaming         bool `json:"streaming"`
    PushNotifications bool `json:"pushNotifications"`
}

type Skill struct {
    ID          string   `json:"id"`
    Name        string   `json:"name"`
    Description string   `json:"description"`
    Tags        []string `json:"tags"`
}

func main() {
    // Connect to the local Pilot daemon
    drv, err := driver.Connect("/tmp/pilot.sock")
    if err != nil {
        log.Fatal(err)
    }
    defer drv.Close()

    // Get our Pilot address for the Agent Card URL
    info, _ := drv.Info()
    pilotAddr, _ := info["address"].(string) // e.g., "1:0000.0042.00A1"

    card := AgentCard{
        Name:        "ResearchAgent",
        Description: "Summarizes academic papers on any topic",
        URL:         fmt.Sprintf("pilot://%s:80", pilotAddr),
        Version:     "1.0.0",
        Capabilities: Capabilities{
            Streaming:         true,
            PushNotifications: false,
        },
        Skills: []Skill{{
            ID:          "summarize-papers",
            Name:        "Paper Summarization",
            Description: "Find and summarize papers on a given topic",
            Tags:        []string{"research", "nlp", "summarization"},
        }},
    }

    // Serve the Agent Card at the A2A well-known path
    http.HandleFunc("/.well-known/agent-card.json", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(card)
    })

    // Handle A2A JSON-RPC task submissions
    http.HandleFunc("/a2a", func(w http.ResponseWriter, r *http.Request) {
        var req struct {
            JSONRPC string          `json:"jsonrpc"`
            Method  string          `json:"method"`
            ID      int             `json:"id"`
            Params  json.RawMessage `json:"params"`
        }
        json.NewDecoder(r.Body).Decode(&req)

        switch req.Method {
        case "tasks/send":
            // Process the A2A task...
            log.Printf("Received A2A task from peer")
            result := processTask(req.Params)
            respondJSON(w, req.ID, result)
        case "tasks/get":
            // Return task status...
            respondJSON(w, req.ID, map[string]string{"status": "completed"})
        }
    })

    // Listen on Pilot's HTTP port (80)
    log.Printf("A2A agent serving at pilot://%s:80", pilotAddr)
    ln, _ := drv.Listen(80)
    http.Serve(ln, http.DefaultServeMux)
}

Any peer agent on the Pilot network can now fetch this agent's capabilities by requesting /.well-known/agent-card.json over a Pilot HTTP connection. The entire exchange -- the Agent Card fetch, the task submission, the result delivery -- travels over Pilot's encrypted UDP tunnel. No public IP required. No DNS. No TLS certificate management. The encryption is handled by Pilot's X25519 + AES-256-GCM layer, which is negotiated automatically during the tunnel handshake.

Trust-Gated Agent Cards

Standard A2A Agent Cards are public. Anyone who knows the URL can fetch the card and see the agent's capabilities. This is fine for agents that want to be discovered, but many agents should not be publicly visible. A medical research agent, a financial analysis agent, or a proprietary data processing agent should only reveal its capabilities to trusted peers.

Pilot's trust model makes Agent Cards private by default. Here is how the layers interact:

  1. Discovery and endpoint access are separate. Directory metadata can remain visible, while open lookups withhold a private agent’s reachable endpoint.
  2. Connection establishment is policy-gated. A private node rejects application connections unless peer trust or an applicable shared network grants access. Peer handshakes are signed with Ed25519.
  3. The Agent Card is served over an authorized connection. By the time a peer fetches /.well-known/agent-card.json, the network admission check has already passed. The card’s contents are revealed through the application path you control.

This creates a layered privacy model around A2A. You control who can reach the application that serves capabilities, not just who can submit tasks. An agent can remain A2A-compatible at the application layer without exposing its Agent Card on a public web endpoint.

Privacy gradient: Public agents serve their Agent Card to anyone who resolves their address. Private agents require a trust handshake before the card is visible. Semi-private agents can serve a redacted card (name and description only) to untrusted peers and the full card (with skills and examples) to trusted ones.

A2A Over Internet vs. A2A Over Pilot

The difference between running A2A on raw internet infrastructure versus running it over Pilot tunnels is significant across every dimension that matters for production agent deployments:

DimensionA2A over InternetA2A over Pilot
ReachabilityRequires public IP or reverse proxyWorks behind any NAT (STUN/hole-punch/relay)
EncryptionTLS certificates (Let's Encrypt, renewal)X25519 + AES-256-GCM, automatic, no certs
DiscoveryDNS + well-known URLRegistry resolve (no DNS needed)
PrivacyAgent Card visible to anyone with URLAgent Card visible only to trusted peers
IdentityDomain name (requires purchase, DNS setup)48-bit virtual address (assigned at first registration)
AuthenticationOAuth, API keys, custom headersEd25519 handshake, signed by identity key
MobilityIP/domain changes break clientsVirtual address is permanent across networks
InfrastructureLoad balancers, CDN, reverse proxypilotctl + pilot-daemon (static binaries) and a registry and beacon (the public ones or your own)

The most significant difference is reachability. A2A over the internet requires every agent to have a stable, publicly accessible URL. This limits A2A to cloud-deployed agents with proper domain configuration. A2A over Pilot works for agents on laptops, edge devices, containers and corporate networks. The Pilot daemon handles NAT traversal transparently, using the same three-tier approach (direct, hole-punch, relay) that makes Pilot work across the full spectrum of network topologies.

A2A Task Lifecycle Over Pilot

A2A defines a structured task lifecycle: a client sends a task, the server processes it (potentially streaming intermediate results), and returns the final output. Over Pilot, this lifecycle maps cleanly to the transport layer:

  1. Discovery. The client agent resolves the server agent's hostname to a Pilot address through the registry (pilotctl find).
  2. Connection. The client opens a Pilot connection to the server on port 80. The tunnel is established through NAT, encryption is negotiated, and the trust handshake completes.
  3. Card fetch. The client sends an HTTP GET to /.well-known/agent-card.json to verify the server's capabilities before submitting work.
  4. Task submission. The client POSTs a JSON-RPC tasks/send request. Pilot's flow control and congestion management ensure reliable delivery even for large payloads.
  5. Streaming. If the task supports streaming, the server sends Server-Sent Events over the same HTTP connection. Pilot's sliding window transport guarantees ordered delivery of every event.
  6. Completion. The server returns the final task result over the established tunnel.

Every step happens over the same encrypted Pilot tunnel. There is no separate TLS negotiation, no certificate rotation, no load balancer configuration. The A2A application logic does not need to know it is running over Pilot -- it sees a standard HTTP connection with request/response semantics. The Pilot driver handles the translation between HTTP and the overlay transport.

The Discovery Pattern

A useful pattern that emerges from combining A2A and Pilot is two-layer discovery. A client agent with a list of candidate hostnames checks both layers:

# Step 1: resolve the candidates you know by hostname
pilotctl find task-worker-a
pilotctl find task-worker-c

# Step 2: fetch each Agent Card over port 80 (a quick check from the shell;
# a real client dials port 80 from code with the driver)
pilotctl connect task-worker-a 80 --message $'GET /.well-known/agent-card.json HTTP/1.0\r\n\r\n'
pilotctl connect task-worker-c 80 --message $'GET /.well-known/agent-card.json HTTP/1.0\r\n\r\n'

# Step 3: task-worker-c lists the summarize-papers skill,
# so POST the JSON-RPC tasks/send request to its /a2a endpoint

Pilot answers which of your candidates exist and accept your connection. A2A's Agent Cards provide the semantic filter -- which of those agents can actually do the specific work needed.

For more on how Pilot's network layer provides the foundation for this kind of integration, see How Pilot Protocol Works. For the complementary pattern using MCP for tool access alongside Pilot networking, see MCP + Pilot: Tools and Network. For a broader protocol comparison, see AI agent communication protocols compared. And for a deeper look at why agents need a dedicated network layer in the first place, see Why AI Agents Need Their Own Network Stack.

Run A2A Over Encrypted Tunnels

Give your A2A agents a network that handles NAT, encryption, and trust. Start with the Pilot daemon and serve Agent Cards in minutes.

View on GitHub