How Pilot Protocol Works
Every modern agent framework makes the same assumption: your agent has a reachable HTTP endpoint. Google's A2A protocol publishes Agent Cards with URLs. Anthropic's MCP connects to servers over stdio or HTTP. LangChain agents call APIs. The entire ecosystem is built on the premise that agents can reach each other via IP addresses and domain names.
The problem is that most of them cannot.
88% of networks involve NAT. An agent running on a developer's laptop, behind a corporate firewall, inside a Docker container, or on a mobile device has no publicly routable address. It cannot receive incoming connections. It cannot be listed in a service registry. It is invisible to every other agent in the world.
Pilot Protocol is the layer that fixes this. It is a UDP overlay network that gives every AI agent a permanent virtual address, encrypted tunnels through NAT, and a cryptographic trust model -- all in a single binary with zero dependencies.
This post walks through the full architecture: how addresses work, what packets look like on the wire, how NAT traversal happens across three tiers, how encryption is negotiated, how trust is established, and what services run on top of the network.
The Problem: Agents Behind NAT Cannot Talk
Consider a simple scenario: you have two AI agents. Agent Alice is running on your laptop in San Francisco. Agent Bob is running on a colleague's workstation in Berlin. You want them to collaborate on a task -- Alice generates a plan, Bob executes it, and they coordinate results.
With HTTP, this is nearly impossible without infrastructure. Alice's laptop is behind a home router performing NAT. Bob's workstation is behind a corporate firewall. Neither has a public IP address. Neither can accept incoming connections.
The standard solutions all add operational overhead:
- Cloud relay -- route everything through a centralized server. Works, but adds latency, cost, and a single point of failure.
- VPN -- tunnel both agents into a shared network. Requires infrastructure, configuration, and ongoing maintenance.
- Port forwarding -- manually configure routers on both sides. Fragile, requires access to network hardware, breaks when IPs change.
- ngrok/Cloudflare Tunnel -- expose local services through reverse proxies. Each agent needs its own tunnel configuration.
All of these solutions treat agent communication as a deployment problem. Pilot Protocol treats it as a networking problem and solves it at the protocol layer.
Addressing: 48-Bit Virtual Addresses
Every agent on the Pilot Protocol network receives a 48-bit virtual address. This address is permanent -- it does not change when the agent moves between networks, when its IP address changes, or when it reconnects after going offline.
The address is composed of two parts:
| Field | Size | Purpose |
|---|---|---|
| Network ID | 16 bits | Identifies which network the agent belongs to |
| Node ID | 32 bits | Unique identifier assigned by the registry |
The text format uses colon-separated hex: N:NNNN.HHHH.LLLL
For example, 0:0000.0000.0001 is node 1 on network 0 (the global backbone). The 16-bit network prefix supports up to 65,535 distinct networks, and within each network, the 32-bit node space supports over 4 billion agents.
Why Not IP Addresses?
IP addresses identify network interfaces, not agents. When an agent moves from Wi-Fi to cellular, its IP changes. When it restarts behind a different NAT, its port changes. When it migrates between cloud regions, everything changes.
Pilot addresses identify agents. An agent's address is derived from its registration with the network's rendezvous server. It persists across restarts, network changes, and migrations. Other agents can always reach it at the same address, regardless of where it physically runs.
Agents can also register hostnames -- human-readable names like alice or data-processor. The protocol's built-in nameserver resolves hostnames to addresses, so you can pilotctl ping alice instead of remembering hex addresses.
Design choice: 48 bits was intentional. It is large enough for global-scale networks but small enough to fit in a fixed-size packet header without variable-length encoding. The 16-bit network prefix enables network isolation -- agents on different networks cannot see each other, even on the same physical infrastructure.
The Packet: 34-Byte Fixed Header
Every Pilot Protocol packet carries a 34-byte fixed header. No variable-length fields, no options, no extensions. This makes parsing trivial and fast -- a single struct read in Go.
| Offset | Field | Size | Description |
|---|---|---|---|
| 0 | Version | 1 byte | Protocol version (currently 1) |
| 1 | Flags | 1 byte | SYN, ACK, FIN, RST, PSH, URG |
| 2 | Protocol | 1 byte | Upper-layer protocol identifier |
| 3 | SrcAddr | 6 bytes | Source virtual address (48-bit) |
| 9 | DstAddr | 6 bytes | Destination virtual address (48-bit) |
| 15 | SrcPort | 2 bytes | Source port number |
| 17 | DstPort | 2 bytes | Destination port number |
| 19 | SeqNum | 4 bytes | Sequence number for ordering |
| 23 | AckNum | 4 bytes | Acknowledgment number |
| 27 | Window | 2 bytes | Advertised receive window (flow control) |
| 29 | Length | 2 bytes | Payload length in bytes |
| 31 | Checksum | 4 bytes | CRC32 over header + payload |
Here is what a SYN packet looks like on the wire, sent from 0:0000.0000.0001 port 1000 to 0:0000.0000.0002 port 7 (echo service):
# Pilot Protocol SYN packet (34 bytes, no payload)
# Offset Hex ASCII
00000000 01 02 00 Version=1 Flags=SYN Proto=0
00000003 00 00 00 00 00 01 SrcAddr=0:0000.0000.0001
00000009 00 00 00 00 00 02 DstAddr=0:0000.0000.0002
0000000F 03 E8 SrcPort=1000
00000011 00 07 DstPort=7 (echo)
00000013 00 00 00 01 SeqNum=1
00000017 00 00 00 00 AckNum=0
0000001B FF FF Window=65535
0000001D 00 00 Length=0 (no payload)
0000001F A3 B2 C1 D0 Checksum (CRC32)
The fixed header design means that every Pilot node -- from a Raspberry Pi to a cloud VM -- can parse packets with the same code, at the same speed. There is no negotiation, no capability exchange, no handshake to determine header format.
Connection Lifecycle
Connections follow a TCP-like lifecycle: SYN, SYN-ACK, ACK for setup, data exchange with sequence numbers and acknowledgments, and FIN for graceful teardown. The transport layer provides:
- Sliding window with configurable size for reliability
- Congestion control using AIMD (Additive Increase / Multiplicative Decrease)
- Flow control via the 2-byte Window field -- receivers advertise available buffer space
- Nagle algorithm to coalesce small writes into larger packets
- Auto segmentation for payloads larger than the MTU
- Zero-window probing to prevent deadlock when the receiver is full
- Keepalive probes every 30 seconds, with 120-second idle timeout
This gives application-layer code a reliable, ordered byte stream -- identical to what net.Conn provides in Go. In fact, Pilot connections implement the net.Conn interface directly, which means standard Go HTTP servers and clients work unmodified over Pilot tunnels.
NAT Traversal: Three Tiers
NAT traversal is the hardest part of any peer-to-peer system. Pilot Protocol solves it with a three-tier approach that handles every NAT type, from simple full-cone NATs to the most restrictive symmetric NATs.
The key component is the beacon -- a lightweight server that runs alongside the rendezvous registry on a public IP. The beacon serves three functions: STUN discovery, hole-punch coordination, and relay fallback.
Tier 1: STUN Discovery
When an agent starts, the first thing it does is discover its own public endpoint. It sends a STUN-like request to the beacon, which reflects back the source IP and port as seen from the public internet.
# Agent sends UDP packet to beacon
Agent --[UDP]--> Beacon (public IP)
# Beacon reads source address from UDP header
# and sends it back as the response payload
Beacon --[UDP]--> Agent: "your public endpoint is 203.0.113.42:54321"
The agent now knows its public IP:port mapping. It registers this endpoint with the rendezvous server. Other agents can look up this endpoint and attempt direct connections.
If the agent is behind a full-cone NAT, this endpoint is valid for all peers. Direct UDP packets to this address will be forwarded to the agent. STUN discovery is all that is needed.
Implementation detail: STUN discovery uses a temporary UDP socket, which is closed before the tunnel binds the same port. This avoids a race condition where the STUN response and tunnel data compete for the same socket.
Tier 2: Hole-Punching
For restricted-cone and port-restricted cone NATs, the STUN-discovered endpoint only works if the agent has already sent a packet to the connecting peer. The NAT will drop incoming packets from unknown sources.
Pilot solves this with beacon-coordinated hole-punching:
- Agent A wants to connect to Agent B
- Agent A sends a
MsgPunchRequestto the beacon, naming Agent B as the target - The beacon sends a
MsgPunchCommandto both agents simultaneously - Both agents send UDP packets to each other's STUN-discovered endpoints at the same time
- The outgoing packets "punch holes" in both NATs, and the incoming packets now have matching entries
- Direct communication is established
The timing is critical -- both sides must send nearly simultaneously for the holes to overlap. The beacon coordinates this by sending the punch commands in the same event loop iteration.
Tier 3: Relay Fallback
Symmetric NATs assign a different external port for every destination. The STUN-discovered endpoint is only valid for the beacon, not for other peers. Hole-punching fails because each side sees a different port.
For these cases, Pilot falls back to relay mode. The beacon acts as a packet relay, wrapping and forwarding traffic between agents:
# MsgRelay format: [0x05][senderNodeID(4)][destNodeID(4)][payload...]
Agent A --[relay]--> Beacon --[relay]--> Agent B
Relay mode is detected automatically. When an agent sends a DialConnection, it first attempts 3 direct retries. If those fail, it automatically switches to relay mode and attempts 3 relay retries through the beacon. The application layer never knows the difference -- it gets the same net.Conn interface either way.
The tradeoff is latency: relay adds one extra hop through the beacon. But it guarantees connectivity for agents behind the most restrictive NATs, including carriers that use CGNAT (Carrier-Grade NAT) for mobile networks.
The Tunnel Layer
All Pilot packets travel inside tunnel frames. A tunnel frame wraps a Pilot packet with metadata for the UDP transport layer:
# Tunnel frame format
50 49 4C 54 # Magic bytes: "PILT" (Pilot Tunnel)
XX XX # Frame length (2 bytes)
[pilot packet] # 34-byte header + payload
The magic bytes serve as a frame delimiter. When reading from a UDP socket, the tunnel layer scans for 0x50494C54 ("PILT") to identify valid frames and discard noise. This makes the protocol robust against packet corruption and injection attempts.
Each agent maintains a single UDP socket for all tunnel traffic. Multiplexing happens at the Pilot layer -- the virtual source and destination addresses in the packet header determine which connection a packet belongs to. This means an agent can maintain thousands of connections to different peers over a single UDP port.
Encryption: X25519 + AES-256-GCM
Pilot Protocol encrypts all traffic by default. There is no unencrypted mode for production use. The encryption system uses X25519 for key exchange and AES-256-GCM for packet encryption, implemented entirely with Go's standard library -- zero external dependencies.
Key Exchange
When two agents establish a tunnel, they perform an X25519 Diffie-Hellman key exchange:
- Each side generates an ephemeral X25519 key pair
- Public keys are exchanged using
PILKframes (magic bytes0x50494C4B) - Both sides compute the shared secret using their private key and the peer's public key
- The shared secret is used to derive the AES-256-GCM encryption key
# Key exchange frame
50 49 4C 4B # Magic bytes: "PILK" (Pilot Key exchange)
[32 bytes] # X25519 public key
Packet Encryption
Once the shared key is established, all subsequent tunnel frames use PILS framing (magic bytes 0x50494C53 -- "Pilot Secure") instead of plain PILT:
# Encrypted frame
50 49 4C 53 # Magic bytes: "PILS" (Pilot Secure)
[12 bytes] # Nonce (random prefix + counter)
[N bytes] # AES-256-GCM ciphertext + 16-byte auth tag
AES-256-GCM provides both confidentiality (encryption) and integrity (authentication). If a single bit is modified in transit, the GCM authentication tag will fail and the packet is discarded.
Replay Prevention
Each secure connection uses a random nonce prefix that is unique per peer pair. The nonce is constructed as: [4-byte random prefix][8-byte counter]. The random prefix ensures that even if two connections use the same counter value, they produce different nonces. The counter is incremented for every packet, preventing replay attacks.
Authentication Frames
The fourth magic byte variant is PILA (0x50494C41 -- "Pilot Authentication"), used during the initial handshake to verify agent identity using Ed25519 signatures before the encrypted channel is established.
Trust Model: Invisible by Default
Most agent frameworks treat discovery as a solved problem. A2A publishes Agent Cards to well-known URLs. MCP servers are configured by the client. The assumption is that agents want to be found.
Pilot Protocol takes the opposite approach: agents are invisible by default.
When a new agent joins the network, it is registered with the rendezvous server but marked as private. This means:
- Lookup (finding an agent by hostname) only returns public agents
- Resolve (getting an agent's endpoint for connection) requires a mutual trust relationship
- Enumeration of the backbone network is blocked -- you cannot list all agents
The Handshake
To establish trust, agents perform a mutual handshake using Ed25519 digital signatures:
- Agent A initiates a handshake to Agent B, including a justification message (why they want to connect)
- The handshake request is signed with Agent A's Ed25519 private key
- The request is relayed through the rendezvous server (since B might not be directly reachable yet)
- Agent B receives the request, verifies the signature, and decides whether to approve
- If approved, Agent B signs an approval response and sends it back
- Both agents now have a mutual trust pair -- they can resolve each other's endpoints and establish tunnels
# Initiate a handshake
$ pilotctl handshake bob "I need access to your data processing service"
# On Bob's side: approve the request
$ pilotctl approve 0:0000.0000.0003
Agents can also configure auto-approval rules -- for example, automatically trusting any agent that provides a specific justification pattern, or auto-approving all agents on the same network.
Revocation
Trust can be revoked instantly with pilotctl untrust. This removes the trust pair, tears down any active tunnels, and notifies the peer. The revoked agent can no longer resolve the revoker's endpoint or establish new connections.
For a deeper exploration of the trust model, see Why Agents Should Be Invisible by Default.
What Runs on Top: Port-Based Services
Like TCP/IP, Pilot Protocol uses port numbers to multiplex multiple services over a single agent address. Several well-known ports are defined:
| Port | Service | Description |
|---|---|---|
| 7 | Echo | Ping/pong for connectivity testing and latency measurement |
| 53 | DNS | Hostname resolution within the overlay network |
| 80 | HTTP | Standard HTTP services over Pilot tunnels |
| 443 | Secure | Encrypted services (X25519 + AES-GCM per connection) |
| 1000 | Stdio | Interactive terminal sessions between agents |
| 1001 | Data Exchange | Structured data and file transfer |
| 1002 | Event Stream | Pub/sub event distribution with topic routing |
| 1003 | Task Submit | Task delegation with capability matching |
The port system means agents can expose multiple services simultaneously. An agent might accept task submissions on port 1003, publish status updates on port 1002, and serve an HTTP API on port 80 -- all on the same virtual address.
HTTP Over Pilot
Because Pilot connections implement Go's net.Conn interface, standard HTTP servers work without modification:
// Standard Go HTTP server running on a Pilot port
listener := pilot.Listen(daemon, 80)
http.Serve(listener, myHandler)
The gateway component bridges Pilot services to the local network. It assigns a loopback IP alias for each Pilot address and listens on common ports, proxying traffic between local HTTP clients and Pilot tunnels. This means existing tools -- curl, browsers, REST clients -- can interact with agents on the Pilot network without any modification. See the Gateway documentation for details.
Pub/Sub and Event Streams
Port 1002 provides a built-in publish/subscribe system with topic routing and wildcard subscriptions. Agents can subscribe to event streams from trusted peers and publish events to subscribers without external message brokers. See Pub/Sub documentation for the full API.
Task Submission
Port 1003 implements a task submission protocol. Agents can advertise capabilities by setting a task-ready flag during registration. Other agents discover capable peers, submit tasks, and receive results -- all over encrypted Pilot tunnels. This is the foundation for building decentralized agent marketplaces without a central orchestrator.
Putting It All Together
Here is the full flow when Agent Alice sends a message to Agent Bob:
- Alice starts -- her daemon performs STUN discovery, registers with the rendezvous server, and gets virtual address
0:0000.0000.0003 - Trust -- Alice and Bob have already completed a mutual handshake (Ed25519 signed)
- Resolve -- Alice asks the rendezvous server for Bob's endpoint. Because they are trusted, the server returns Bob's public IP:port
- Tunnel -- Alice's daemon opens a UDP tunnel to Bob's endpoint. If NAT blocks direct connection, the beacon coordinates hole-punching or falls back to relay
- Encrypt -- X25519 key exchange produces a shared secret. All subsequent frames use AES-256-GCM with
PILSframing - Connect -- Alice opens a connection to Bob on port 1001 (Data Exchange). SYN/SYN-ACK/ACK over the encrypted tunnel
- Send -- The message is segmented if needed, transmitted with sequence numbers, acknowledged, and reassembled on Bob's side
- Receive -- Bob's application reads the message from the
net.Conninterface, exactly as if it were a regular TCP connection
All of this happens in milliseconds. The agent developer writes standard Go networking code. The Pilot daemon handles everything else: address resolution, NAT traversal, encryption, flow control, and reliable delivery.
226 tests validate every layer of the stack, from packet serialization to end-to-end NAT traversal. The protocol is implemented in pure Go with zero external dependencies. See the Core Concepts documentation for the complete technical reference.
Try Pilot Protocol
Set up a multi-agent network in under 5 minutes. One binary, zero dependencies.
View on GitHub
Pilot Protocol