Pilot Protocol
The network stack for AI agents.
Addresses. Ports. Tunnels. Encryption. Trust. Zero dependencies.
Wire Spec · Whitepaper · Agent Skills · Vulture Labs
The internet was built for humans. AI agents have no address, no identity, no way to be reached. Pilot Protocol is an overlay network that gives agents what the internet gave devices: a permanent address, encrypted peer-to-peer channels, and a trust model -- all layered on top of standard UDP.
It is not an API. It is not a framework. It is infrastructure.
The problem
Today, agents talk through centralized APIs. Every connection requires a platform in the middle. There is no way for two agents to find each other, establish trust, or communicate directly.
graph LR
A1[Agent A] -->|HTTP API| P[Platform / Cloud]
A2[Agent B] -->|HTTP API| P
A3[Agent C] -->|HTTP API| P
style P fill:#f66,stroke:#333,color:#fff
style A1 fill:#4a9,stroke:#333,color:#fff
style A2 fill:#4a9,stroke:#333,color:#fff
style A3 fill:#4a9,stroke:#333,color:#fff
Pilot Protocol removes the middleman. Each agent gets a permanent address and talks directly to peers over encrypted tunnels:
graph LR
A1[Agent A<br/><small>0:0000.0000.0001</small>] <-->|Encrypted UDP Tunnel| A2[Agent B<br/><small>0:0000.0000.0002</small>]
A1 <-->|Encrypted UDP Tunnel| A3[Agent C<br/><small>0:0000.0000.0003</small>]
A2 <-->|Encrypted UDP Tunnel| A3
style A1 fill:#4a9,stroke:#333,color:#fff
style A2 fill:#4a9,stroke:#333,color:#fff
style A3 fill:#4a9,stroke:#333,color:#fff
What agents get
pilotctl info # Who am I?
pilotctl set-hostname my-agent # Claim a name
pilotctl find other-agent # Discover a peer
pilotctl send other-agent 1000 --data "hello" # Send a message
pilotctl recv 1000 --count 5 --timeout 30s # Listen for messages
Every command supports --json for structured output. Every error has a machine-readable code and an actionable hint. No interactive prompts.
Example JSON output
$ pilotctl --json info {"status":"ok","data":{"address":"0:0000.0000.0005","node_id":5,"hostname":"my-agent","peers":3,"connections":1,"uptime_secs":3600}} $ pilotctl --json find other-agent {"status":"ok","data":{"hostname":"other-agent","address":"0:0000.0000.0003"}} $ pilotctl --json recv 1000 --count 1 {"status":"ok","data":{"messages":[{"seq":0,"port":1000,"data":"hello","bytes":5}]}} $ pilotctl --json find nonexistent {"status":"error","code":"not_found","message":"hostname not found: nonexistent","hint":"check the hostname and ensure mutual trust exists"}
Highlights
|
Addressing
Transport
|
Security
Operations
|
Architecture
graph LR
subgraph Local Machine
Agent[Your Agent] -->|commands| CLI[pilotctl]
CLI -->|Unix socket| D[Daemon]
D --- E[Echo :7]
D --- DX[Data Exchange :1001]
D --- ES[Event Stream :1002]
end
D <====>|UDP Tunnel<br/>AES-256-GCM + NAT traversal| RD
subgraph Remote Machine
RD[Remote Daemon] -->|Unix socket| RC[pilotctl]
RC -->|commands| RA[Remote Agent]
RD --- RE[Echo :7]
RD --- RDX[Data Exchange :1001]
RD --- RES[Event Stream :1002]
end
D -.->|register + discover| RV
RD -.->|register + discover| RV
subgraph Rendezvous
RV[Registry :9000<br/>Beacon :9001]
end
Your agent talks to a local daemon over a Unix socket. The daemon handles everything: tunnel encryption, NAT traversal, packet routing, congestion control, connection management, and built-in services. You never touch sockets, ports, or crypto directly.
The daemon connects to a rendezvous server that combines two roles:
- Registry (TCP :9000) -- node directory, trust relay, state persistence
- Beacon (UDP :9001) -- STUN-based NAT traversal
Connection lifecycle
sequenceDiagram
participant A as Agent A
participant DA as Daemon A
participant R as Registry
participant DB as Daemon B
participant B as Agent B
Note over DA, DB: Both daemons register on startup
A->>DA: pilotctl handshake agent-b
DA->>R: Handshake request (signed Ed25519)
R->>DB: Relay handshake
DB->>B: Pending trust request
B->>DB: pilotctl approve <node_id>
DB->>R: Approval (signed)
R->>DA: Trust established
Note over DA, DB: Mutual trust established
A->>DA: pilotctl connect agent-b --message "hello"
DA->>R: Resolve hostname
R-->>DA: Address + endpoint
DA->>DB: SYN (UDP tunnel, encrypted)
DB-->>DA: SYN-ACK
DA->>DB: ACK + data
DB->>B: Deliver message
Gateway bridging
graph LR
subgraph Standard Tools
C[curl / browser]
end
subgraph Gateway Machine
C -->|TCP| GW[Gateway]
GW -->|Pilot Protocol| D[Daemon]
end
D <====>|Encrypted UDP Tunnel| RD[Remote Daemon]
subgraph Remote Machine
RD --> WS[Webserver :80<br/>or any port]
end
NAT traversal
Pilot Protocol handles NAT automatically with a three-tier connection strategy:
- Direct -- If both peers have public endpoints (or Full Cone NAT), the tunnel connects directly using the STUN-discovered address.
- Hole-punch -- For Restricted/Port-Restricted Cone NAT, the beacon coordinates simultaneous UDP sends from both sides to punch through the NAT.
- Relay -- When hole-punching fails (Symmetric NAT), traffic is relayed through the beacon transparently. The daemon auto-detects and falls back.
The beacon also sends periodic heartbeat keepalives to maintain NAT port mappings. Cloud NAT (GCP, AWS) uses Endpoint-Independent Mapping, so direct connections work even between two NATted nodes.
No configuration is needed -- the daemon handles everything on startup.
Demo
A public demo agent (agent-alpha) is running on the Pilot Protocol network with auto-accept enabled. You can connect to its website from your machine:
# 1. Install curl -fsSL https://raw.githubusercontent.com/TeoSlayer/pilotprotocol/main/install.sh | sh # 2. Start the daemon pilotctl daemon start --hostname my-agent # 3. Request trust (auto-approved within seconds) pilotctl handshake agent-alpha "hello" # 4. Wait a few seconds, then verify trust pilotctl trust # 5. Start the gateway (maps the agent to a local IP) sudo pilotctl gateway start --ports 80 0:0000.0000.0004 # 6. Open the website curl http://10.4.0.1/ curl http://10.4.0.1/status
You can also ping and benchmark:
pilotctl ping agent-alpha pilotctl bench agent-alpha
Install
curl -fsSL https://raw.githubusercontent.com/TeoSlayer/pilotprotocol/main/install.sh | shThe installer handles everything:
- Detects your platform (linux/darwin, amd64/arm64)
- Downloads pre-built binaries from the latest release (falls back to building from source if Go is available)
- Installs
pilot-daemon,pilotctl, andpilot-gatewayto~/.pilot/bin(no sudo needed) - Adds
~/.pilot/binto your PATH - Writes
~/.pilot/config.jsonwith the public rendezvous server pre-configured - Sets up a system service:
- Linux: creates a
systemdunit (pilot-daemon.service) - macOS: creates a
launchdagent (com.vulturelabs.pilot-daemon)
- Linux: creates a
Set a hostname during install with the PILOT_HOSTNAME environment variable:
curl -fsSL https://raw.githubusercontent.com/TeoSlayer/pilotprotocol/main/install.sh | PILOT_HOSTNAME=my-agent shUninstall
curl -fsSL https://raw.githubusercontent.com/TeoSlayer/pilotprotocol/main/install.sh | sh -s uninstallStops the daemon, removes the system service, deletes binaries, config (~/.pilot/), and the IPC socket.
From source
git clone https://github.com/TeoSlayer/pilotprotocol.git
cd pilotprotocol
make buildBinaries
| Binary | Description |
|---|---|
pilot-daemon |
Core network agent. Manages tunnel, connections, and built-in services (echo, data exchange, event stream) |
pilotctl |
CLI tool for agents and operators. All daemon interaction goes through this |
pilot-gateway |
IP-to-Pilot bridge. Maps pilot addresses to local IPs for standard TCP tools (curl, browsers) |
Server binaries (rendezvous, registry, beacon) exist in cmd/ for running your own infrastructure.
Quick start
1. Start the daemon
pilotctl daemon start --hostname my-agent
Connects to the public rendezvous server automatically. The daemon auto-starts built-in services and runs in the background.
The daemon auto-starts three built-in services:
| Port | Service | Description | Disable flag |
|---|---|---|---|
| 7 | Echo | Liveness probes, latency measurement, benchmarks | -no-echo |
| 1001 | Data Exchange | Typed frames (text, JSON, binary, file) with ACK | -no-dataexchange |
| 1002 | Event Stream | Pub/sub broker with topic filtering and wildcards | -no-eventstream |
2. Use it
# Check status pilotctl info # Ping a peer pilotctl ping other-agent # Send a message pilotctl connect other-agent --message "hello" # Transfer a file (saved to ~/.pilot/received/ on target) pilotctl send-file other-agent ./data.json # Send a typed message pilotctl send-message other-agent --data '{"status":"ready"}' --type json # Subscribe to events (streams until Ctrl+C) pilotctl subscribe other-agent status # Publish an event pilotctl publish other-agent status --data "online" # Run throughput benchmark (1 MB default) pilotctl bench other-agent
Commands
Identity & Discovery
| Command | Description |
|---|---|
info |
Your address, hostname, node ID, status, connection table |
set-hostname <name> |
Claim a hostname on the network |
clear-hostname |
Remove your hostname |
find <hostname> |
Look up another agent by name |
set-public |
Make this node visible to all |
set-private |
Hide this node (default) |
Communication
| Command | Description |
|---|---|
send <addr|hostname> <port> --data <msg> |
Send a message to a port |
recv <port> [--count n] [--timeout dur] |
Listen for incoming messages |
connect <addr|hostname> [port] --message <msg> |
Send a message and get a response (default port: 1000). Supports pipe mode via stdin |
send-file <addr|hostname> <path> |
Transfer a file via data exchange (port 1001). Saved to ~/.pilot/received/ on target |
send-message <addr|hostname> --data <text> [--type text|json|binary] |
Send a typed message via data exchange (port 1001). Saved to ~/.pilot/inbox/ on target |
subscribe <addr|hostname> <topic> [--count n] [--timeout dur] |
Subscribe to event stream topics (port 1002). Use * for all topics |
publish <addr|hostname> <topic> --data <msg> |
Publish an event to a topic on the target's event stream broker |
listen <port> [--count n] |
Raw port listener (NDJSON in --json mode) |
broadcast <network_id> <message> |
Broadcast to all nodes on a network |
Trust
Agents are private by default. Two agents must establish mutual trust before they can communicate.
| Command | Description |
|---|---|
handshake <hostname> [reason] |
Request trust (auto-approves if mutual). Relayed via registry for NAT traversal |
pending |
See incoming trust requests (persisted across restarts) |
approve <node_id> |
Accept a request |
reject <node_id> [reason] |
Decline a request |
trust |
List trusted peers |
untrust <node_id> |
Revoke trust |
Daemon Lifecycle
| Command | Description |
|---|---|
daemon start [flags] |
Start the daemon (background by default) |
daemon stop |
Stop the running daemon |
daemon status [--check] |
Show status (--check: silent exit 0/1 for scripts) |
Mailbox
Received files and messages are stored locally and can be inspected at any time.
| Command | Description |
|---|---|
received [--clear] |
List files received via data exchange. Files saved to ~/.pilot/received/ |
inbox [--clear] |
List messages received via data exchange. Messages saved to ~/.pilot/inbox/ |
Diagnostics
| Command | Description |
|---|---|
ping <addr|hostname> [--count n] |
Echo probes for reachability and latency |
bench <addr|hostname> [size_mb] |
Throughput benchmark via echo service |
traceroute <addr> |
Connection setup time and RTT samples |
peers [--search query] |
Connected peers with encryption status |
connections |
Active connections with per-conn stats (CWND, SRTT, flight) |
disconnect <conn_id> |
Close a connection |
Gateway
The gateway bridges standard IP/TCP traffic to Pilot Protocol. Maps pilot addresses to local IPs on a private subnet, starts TCP proxy listeners. Requires root for ports below 1024.
| Command | Description |
|---|---|
gateway start [--subnet cidr] [--ports list] [addrs...] |
Start the IP-to-Pilot bridge |
gateway stop |
Stop the gateway |
gateway map <pilot-addr> [local-ip] |
Add a mapping |
gateway unmap <local-ip> |
Remove a mapping |
gateway list |
Show active mappings |
Example:
sudo pilotctl gateway start 0:0000.0000.0001 # mapped 10.4.0.1 -> 0:0000.0000.0001 curl http://10.4.0.1:3000/status # {"status":"ok","protocol":"pilot","port":3000}
Registry Operations
| Command | Description |
|---|---|
register [listen_addr] |
Register a node |
lookup <node_id> |
Look up a node |
deregister |
Deregister this node from the registry |
rotate-key <node_id> <owner> |
Rotate Ed25519 keypair via owner recovery |
Agent Integration
| Command | Description |
|---|---|
context |
Machine-readable command catalog with args, flags, return schemas, and error codes |
config [--set key=value] |
Show or update configuration |
Well-known ports
| Port | Service | Built-in | Description |
|---|---|---|---|
| 0 | Ping | Yes | Internal control |
| 7 | Echo | Yes | Liveness and latency testing |
| 53 | Nameserver | No | DNS-equivalent name resolution (WIP) |
| 80 | HTTP | No | Standard web endpoints (use with gateway) |
| 443 | Secure | No | End-to-end encrypted channel (X25519 + AES-GCM) |
| 444 | Handshake | Yes | Trust negotiation protocol |
| 1000 | Stdio | No | Text streams between agents |
| 1001 | Data Exchange | Yes | Typed frames (text, JSON, binary, file) |
| 1002 | Event Stream | Yes | Pub/sub with topic filtering |
Deployment
Daemon flags
-registry Registry address (default: 35.193.106.76:9000)
-beacon Beacon address (default: 35.193.106.76:9001)
-listen UDP tunnel address (default: :0)
-socket IPC socket path (default: /tmp/pilot.sock)
-identity Path to persist Ed25519 identity
-hostname Hostname for discovery
-encrypt Enable tunnel encryption (default: true)
-public Make node publicly visible (default: false)
-owner Owner email for key rotation recovery
-no-echo Disable built-in echo service (port 7)
-no-dataexchange Disable built-in data exchange service (port 1001)
-no-eventstream Disable built-in event stream service (port 1002)
-log-level Log level: debug, info, warn, error (default: info)
-log-format Log format: text, json (default: text)
-config Path to JSON config file
Rendezvous flags
-registry-addr Registry listen address (default: :9000)
-beacon-addr Beacon listen address (default: :9001)
-store Path to persist registry state (JSON snapshot)
-tls Enable TLS for registry connections
-tls-cert TLS certificate file
-tls-key TLS key file
-standby Run as hot standby replicating from a primary address
Environment variables
| Variable | Default | Description |
|---|---|---|
PILOT_SOCKET |
/tmp/pilot.sock |
Daemon IPC socket path |
PILOT_REGISTRY |
35.193.106.76:9000 |
Registry server address |
Persistence with systemd
[Unit] Description=Pilot Protocol Daemon After=network.target [Service] Type=simple User=pilot ExecStart=/usr/local/bin/pilot-daemon \ -registry 35.193.106.76:9000 \ -beacon 35.193.106.76:9001 \ -listen :4000 \ -socket /tmp/pilot.sock \ -identity /var/lib/pilot/identity.json \ -encrypt -public \ -hostname my-agent Restart=on-failure [Install] WantedBy=multi-user.target
Testing
go test -parallel 4 -count=1 ./tests/223 tests pass, 24 skipped (IPv6, platform-specific). The -parallel 4 flag is required -- unlimited parallelism exhausts ports and causes dial timeouts.
Error codes
Every error includes a hint field telling you what to do next.
| Code | Meaning | Retry? |
|---|---|---|
invalid_argument |
Bad input or usage error | No |
not_found |
Resource not found (hostname/node) | No |
already_exists |
Duplicate operation (daemon/gateway already running) | No |
not_running |
Service not available (daemon/gateway not running) | No |
connection_failed |
Network or dial failure | Yes |
timeout |
Operation timed out | Yes (with longer timeout) |
internal |
Unexpected system error | Maybe |
Documentation
| Document | Description |
|---|---|
| Wire Specification | Packet format, addressing, flags, checksums |
| Whitepaper (PDF) | Full protocol design, transport, security, validation |
| Agent Skills | Machine-readable skill definition for AI agent integration |
| Contributing | Guidelines for contributing to the project |
License
Pilot Protocol is licensed under the GNU Affero General Public License v3.0.
Vulture Labs
Built for agents, by humans.
