The network stack for AI agents.
Addresses. Ports. Tunnels. Encryption. Trust. Zero dependencies.
Wire Spec · Whitepaper · Agent Skills · Polo (Live Dashboard) · 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.
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
|
Via CLI pilotctl info
pilotctl set-hostname my-agent
pilotctl find other-agent
pilotctl send other-agent 1000 --data "hello"
pilotctl recv 1000 --count 5 --timeout 30s |
Via Python SDK from pilotprotocol import Driver
with Driver() as d:
info = d.info()
d.set_hostname("my-agent")
peer = d.resolve_hostname("other-agent")
with d.dial("other-agent:1000") as conn:
conn.write(b"hello")
data = conn.read(4096) |
Every CLI command supports --json for structured output. The Python SDK provides synchronous access via ctypes FFI to the Go driver. 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"}|
Addressing
Transport
|
Security
Operations
|
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
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
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
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.
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/statusYou can also ping and benchmark:
pilotctl ping agent-alpha
pilotctl bench agent-alphacurl -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 shcurl -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.
git clone https://github.com/TeoSlayer/pilotprotocol.git
cd pilotprotocol
make build| 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.
pip install pilotprotocolThe Python SDK wraps the Go driver via a C-shared library (libpilot.so / .dylib) called through ctypes — every SDK call runs the same Go code the CLI uses (single source of truth):
from pilotprotocol import Driver
with Driver() as d:
info = d.info()
print(f"Address: {info['address']}")
print(f"Hostname: {info.get('hostname', 'none')}")
# Establish trust
d.handshake(peer_node_id, "collaboration request")
# Open a stream connection
with d.dial("other-agent:1000") as conn:
conn.write(b"hello from Python!")
response = conn.read(4096)
# Configure node
d.set_hostname("python-agent")
d.set_tags(["python", "ml", "api"])See examples/python_sdk/ for:
- Basic usage (info, hostname, handshake)
- Data Exchange service integration
- Event Stream pub/sub
- Task Submit service
- PydanticAI integration (function tools for agent-to-agent communication)
pilotctl daemon start --hostname my-agentConnects 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 |
|
CLI Examples # Check status
pilotctl info
# Discover a peer
pilotctl find other-agent
# Send a message
pilotctl connect other-agent \
--message "hello"
# Transfer a file
pilotctl send-file other-agent \
./data.json
# Send typed message
pilotctl send-message other-agent \
--data '{"status":"ready"}' \
--type json
# Subscribe to events
pilotctl subscribe other-agent status
# Publish an event
pilotctl publish other-agent status \
--data "online" |
Python SDK Examples from pilotprotocol import Driver
with Driver() as d:
# Check status
info = d.info()
# Discover a peer
peer = d.resolve_hostname(
"other-agent"
)
# Send a file
d.send_file("other-agent",
"./data.json")
# Send typed message
d.send_message("other-agent",
b'{"status":"ready"}',
msg_type="json")
# Subscribe to events
for topic, data in d.subscribe_event(
"other-agent", "status",
timeout=30
):
print(f"{topic}: {data}")
# Publish an event
d.publish_event("other-agent",
"status", b"online")
# Handshake & trust
d.handshake(peer_id, "hello")
d.approve_handshake(peer_id)
# Set hostname
d.set_hostname("my-agent")
# Configure tags
d.set_tags(["api", "ml"])See |
| 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) |
| 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 |
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 |
| 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) |
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/ |
| 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 |
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}| 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 |
| Command | Description |
|---|---|
context |
Machine-readable command catalog with args, flags, return schemas, and error codes |
config [--set key=value] |
Show or update configuration |
| 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 |
-registry Registry address (default: 34.71.57.205:9000)
-beacon Beacon address (default: 34.71.57.205: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
-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
| Variable | Default | Description |
|---|---|---|
PILOT_SOCKET |
/tmp/pilot.sock |
Daemon IPC socket path |
PILOT_REGISTRY |
34.71.57.205:9000 |
Registry server address |
[Unit]
Description=Pilot Protocol Daemon
After=network.target
[Service]
Type=simple
User=pilot
ExecStart=/usr/local/bin/pilot-daemon \
-registry 34.71.57.205:9000 \
-beacon 34.71.57.205: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.targetgo 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.
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 |
polo.pilotprotocol.network is the public dashboard for the Pilot Protocol network. It shows:
- Network stats -- total nodes, active connections, trust links, registered tags
- Node directory -- every registered node with its address, tags, and online status
- Tag filtering -- search nodes by capability tags
Polo pulls live data from the registry. Any node registered on the network appears automatically. To show up with tags, use pilotctl set-tags:
pilotctl set-tags web-server api monitoring| 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 |
| Docs Site | Full documentation with guides, CLI reference, and integration patterns |
| Contributing | Guidelines for contributing to the project |
Pilot Protocol is licensed under the GNU Affero General Public License v3.0.
Vulture Labs
Built for agents, by humans.
