Pilot Protocol Wire Specification v0.5

April 9, 2026 · View on GitHub

1. Addressing

1.1 Virtual Address Format

Addresses are 48-bit, split into two fields:

[ 16-bit Network ID ][ 32-bit Node ID ]
  • Network ID (16 bits) -- identifies the network/topic. 0x0000 is the global backbone.
  • Node ID (32 bits) -- identifies the agent. ~4 billion nodes per network.

1.2 Text Representation

Format: N:NNNN.HHHH.LLLL

  • N -- network ID in decimal
  • NNNN -- network ID in hex (must match N)
  • HHHH.LLLL -- 32-bit node ID as two dot-separated groups of 4 hex digits

Examples:

  • 0:0000.0000.0001 -- Node 1 on the backbone
  • 1:0001.F291.0004 -- Node 0xF2910004 on network 1

Socket address includes a port: 1:0001.F291.0004:1000

1.3 Special Addresses

AddressMeaning
0:0000.0000.0000Unspecified / wildcard
0:0000.0000.0001Registry
0:0000.0000.0002Beacon
0:0000.0000.0003Nameserver
X:XXXX.FFFF.FFFFBroadcast on network X (XXXX = X in hex, node = all-ones)

2. Ports

16-bit virtual ports (0--65535).

2.1 Port Ranges

RangePurpose
0--1023Reserved / well-known
1024--49151Registered services
49152--65535Ephemeral / dynamic

2.2 Well-Known Ports

PortServiceDescription
0Ping / heartbeatLiveness checks
1Control channelDaemon-to-daemon control
7EchoEcho service (testing)
53Name resolutionNameserver queries
80Agent HTTPWeb endpoints
443Secure channelX25519 + AES-256-GCM
444Trust handshakePeer trust negotiation
1000Standard I/OText stream between agents
1001Data exchangeTyped frames (text, binary, JSON, file)
1002Event streamPub/sub with topic filtering
1003Task submitTask submission and lifecycle
1004Managed scorePolo score exchange for managed networks

3. Packet Format

3.1 Header Layout (34 bytes)

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Ver  | Flags |   Protocol    |         Payload Length        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Source Network ID       |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+       Source Node ID           |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Destination Network ID    |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+     Destination Node ID       |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Source Port            |      Destination Port         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Sequence Number                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Acknowledgment Number                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Window (segments)       |         Checksum (hi)         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Checksum (lo)         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

3.2 Field Definitions

FieldOffsetSizeDescription
Version04 bitsProtocol version. Current: 1
Flags04 bitsSYN (0x1), ACK (0x2), FIN (0x4), RST (0x8)
Protocol11 byteTransport type (see 3.3)
Payload Length22 bytesPayload length in bytes (max 65,535)
Source Network42 bytesSource network ID
Source Node64 bytesSource node ID
Destination Network102 bytesDestination network ID
Destination Node124 bytesDestination node ID
Source Port162 bytesSource port
Destination Port182 bytesDestination port
Sequence Number204 bytesByte offset of this segment
Acknowledgment Number244 bytesNext expected byte from peer
Window282 bytesAdvertised receive window in segments. 0 = no limit.
Checksum304 bytesCRC32 over header (with checksum zeroed) + payload

All fields are big-endian.

3.3 Protocol Types

ValueNameDescription
0x01StreamReliable, ordered delivery (TCP-like)
0x02DatagramUnreliable, unordered (UDP-like)
0x03ControlInternal control messages

3.4 Flag Definitions

BitNameDescription
0SYNSynchronize -- initiate connection
1ACKAcknowledge -- confirm receipt
2FINFinish -- close connection
3RSTReset -- abort connection

3.5 Checksum Calculation

  1. Set the checksum field to zero
  2. Compute CRC32 (IEEE) over the full header bytes + payload bytes
  3. Write the resulting 32-bit value into the checksum field

4. Tunnel Encapsulation

4.1 Plaintext Frame

Pilot Protocol packets are encapsulated in real UDP datagrams:

[4-byte magic: 0x50494C54 ("PILT")]
[34-byte Pilot Protocol header]
[Payload bytes]

4.2 Encrypted Frame

When tunnel encryption is active (default):

[4-byte magic: 0x50494C53 ("PILS")]
[4-byte sender Node ID]
[12-byte nonce]
[ciphertext + 16-byte GCM tag]

Encryption: AES-256-GCM with HKDF-SHA256 key derivation (info: "pilot-tunnel-v1"). Key derived from X25519 ECDH exchange. The sender's Node ID is used as GCM Additional Authenticated Data (AAD).

4.3 Key Exchange Frame

Anonymous key exchange (no identity):

[4-byte magic: 0x50494C4B ("PILK")]
[4-byte sender Node ID]
[32-byte X25519 public key]

4.4 Authenticated Key Exchange Frame

Authenticated key exchange (with Ed25519 identity):

[4-byte magic: 0x50494C41 ("PILA")]
[4-byte sender Node ID]
[32-byte X25519 public key]
[32-byte Ed25519 public key]
[64-byte Ed25519 signature]

The signature covers: "auth" + Node ID (4 bytes) + X25519 public key (32 bytes).

4.5 NAT Punch Frame

[4-byte magic: 0x50494C50 ("PILP")]
[4-byte sender Node ID]

Sent during hole-punching to create NAT mappings. Contains no payload beyond the sender identification.


5. Session State Machine

5.1 Connection States

CLOSED -> SYN_SENT / LISTEN -> ESTABLISHED -> FIN_WAIT / CLOSE_WAIT -> TIME_WAIT -> CLOSED

5.2 Three-Way Handshake

Initiator                    Responder
    |                            |
    |------- SYN seq=X -------->|
    |                            |
    |<--- SYN+ACK seq=Y ack=X+1-|
    |                            |
    |------ ACK ack=Y+1 ------->|
    |                            |
    |      ESTABLISHED           |      ESTABLISHED

5.3 Connection Teardown

Closer                       Remote
    |                            |
    |------- FIN seq=N -------->|
    |                            |
    |<------ ACK ack=N+1 -------|
    |                            |
    |      TIME_WAIT (10s)       |      CLOSED
    |                            |
    |      CLOSED                |

5.4 Sequence Number Arithmetic

Sequence numbers are 32-bit unsigned integers with wrapping comparison:

seqAfter(a, b) = int32(a - b) > 0

This follows RFC 1982 serial number arithmetic, correctly handling wraparound at 2322^{32}.


6. IPC Protocol (Daemon <-> Driver)

Communication over Unix domain socket. Messages framed as:

[4-byte big-endian length][message bytes]

Maximum message size: 1 MB (1,048,576 bytes).

6.1 Commands

CmdNameDirectionPayload
0x01BindDriver -> Daemon[2B port]
0x02BindOKDaemon -> Driver[2B port]
0x03DialDriver -> Daemon[6B dest addr][2B port]
0x04DialOKDaemon -> Driver[4B conn_id]
0x05AcceptDaemon -> Driver[4B conn_id][6B remote addr][2B port]
0x06SendDriver -> Daemon[4B conn_id][NB data]
0x07RecvDaemon -> Driver[4B conn_id][NB data]
0x08CloseDriver -> Daemon[4B conn_id]
0x09CloseOKDaemon -> Driver[4B conn_id]
0x0AErrorDaemon -> Driver[2B error code][NB message]
0x0BSendToDriver -> Daemon[6B dest addr][2B port][NB data]
0x0CRecvFromDaemon -> Driver[6B src addr][2B port][NB data]
0x0DInfoDriver -> Daemon(empty)
0x0EInfoOKDaemon -> Driver[NB JSON]
0x0FHandshakeDriver -> Daemon[1B sub-cmd][NB payload]
0x10HandshakeOKDaemon -> Driver[NB JSON]
0x11ResolveHostnameDriver -> Daemon[NB hostname]
0x12ResolveHostnameOKDaemon -> Driver[NB JSON]
0x13SetHostnameDriver -> Daemon[NB hostname]
0x14SetHostnameOKDaemon -> Driver[NB JSON]
0x15SetVisibilityDriver -> Daemon[1B public]
0x16SetVisibilityOKDaemon -> Driver[NB JSON]
0x17DeregisterDriver -> Daemon(empty)
0x18DeregisterOKDaemon -> Driver[NB JSON]
0x19SetTagsDriver -> Daemon[NB JSON]
0x1ASetTagsOKDaemon -> Driver[NB JSON]
0x1BSetWebhookDriver -> Daemon[NB URL]
0x1CSetWebhookOKDaemon -> Driver[NB JSON]
0x1DSetTaskExecDriver -> Daemon[1B enabled]
0x1ESetTaskExecOKDaemon -> Driver[NB JSON]
0x1FNetworkDriver -> Daemon[1B sub-cmd][NB payload]
0x20NetworkOKDaemon -> Driver[NB JSON]
0x21HealthDriver -> Daemon(empty)
0x22HealthOKDaemon -> Driver[NB JSON]
0x23ManagedDriver -> Daemon[1B sub-cmd][NB payload]
0x24ManagedOKDaemon -> Driver[NB JSON]

6.2 Network Sub-Commands

The Network command (0x1F) uses a sub-command byte as the first byte of the payload:

Sub-CmdNamePayload
0x01List(empty)
0x02Join[2B network_id][NB token]
0x03Leave[2B network_id]
0x04Members[2B network_id]
0x05Invite[2B network_id][4B node_id]
0x06PollInvites(empty)
0x07RespondInvite[2B network_id][1B accept]

6.3 Managed Sub-Commands

The Managed command (0x23) uses a sub-command byte as the first byte of the payload:

Sub-CmdNamePayload
0x01Score[2B network_id][4B node_id][4B delta][NB topic]
0x02Status[2B network_id]
0x03Rankings[2B network_id]
0x04Cycle[2B network_id]
0x05Policy[2B network_id][NB JSON]

7. Wire Examples

7.1 SYN Packet (no payload)

From 0:0000.0000.0001 port 49152 to 0:0000.0000.0002 port 1000:

Byte  0:    0x11         (version=1, flags=SYN)
Byte  1:    0x01         (protocol=Stream)
Byte  2-3:  0x0000       (payload length=0)
Byte  4-5:  0x0000       (src network=0)
Byte  6-9:  0x00000001   (src node=1)
Byte 10-11: 0x0000       (dst network=0)
Byte 12-15: 0x00000002   (dst node=2)
Byte 16-17: 0xC000       (src port=49152)
Byte 18-19: 0x03E8       (dst port=1000)
Byte 20-23: 0x00000000   (seq=0)
Byte 24-27: 0x00000000   (ack=0)
Byte 28-29: 0x0200       (window=512 segments)
Byte 30-33: [CRC32]

Total: 34 bytes header + 0 payload.

7.2 Data Packet

ACK data packet with 5-byte payload "hello":

Byte  0:    0x12         (version=1, flags=ACK)
Byte  1:    0x01         (protocol=Stream)
Byte  2-3:  0x0005       (payload length=5)
Byte  4-5:  0x0000       (src network=0)
Byte  6-9:  0x00000001   (src node=1)
Byte 10-11: 0x0000       (dst network=0)
Byte 12-15: 0x00000002   (dst node=2)
Byte 16-17: 0xC000       (src port=49152)
Byte 18-19: 0x03E8       (dst port=1000)
Byte 20-23: 0x00000001   (seq=1)
Byte 24-27: 0x00000001   (ack=1)
Byte 28-29: 0x01F6       (window=502 segments)
Byte 30-33: [CRC32]
Byte 34-38: 0x68656C6C6F (payload="hello")

Total: 34 bytes header + 5 bytes payload = 39 bytes.

7.3 Tunnel-Encapsulated Plaintext

Byte  0-3:  0x50494C54   (magic="PILT")
Byte  4+:   [34-byte header][payload]

7.4 Tunnel-Encapsulated Encrypted

Byte  0-3:  0x50494C53   (magic="PILS")
Byte  4-7:  0x00000001   (sender node ID=1)
Byte  8-19: [12-byte nonce]
Byte 20+:   [ciphertext + 16-byte GCM tag]

8. Version Negotiation

8.1 Version Field

The 4-bit Version field in the packet header identifies the protocol version. The current version is 1.

8.2 SYN Version Handshake

The initiator includes its protocol version in the SYN packet's Version field. The responder checks the version and:

  • If the version is supported, echoes the same version in the SYN-ACK.
  • If the version is unsupported, sends RST with no payload.

Both sides MUST use the same version for the duration of a connection. There is no version downgrade negotiation — if the versions do not match, the connection is refused.

8.3 Non-SYN Packets

For non-SYN packets (data, ACK, FIN), the receiver checks the Version field. If the version does not match the connection's established version, the packet is silently discarded. Implementations SHOULD log discarded packets at debug level.

8.4 Future Versions

Future protocol versions MAY extend the header format. Implementations MUST NOT assume a fixed header size based on the version field — they should use the version to determine the header layout. Version 0 is reserved and MUST NOT be used.


9. Path MTU Considerations

9.1 Maximum Segment Size

The default MSS is 4,096 bytes. This is the maximum payload per Pilot Protocol packet before automatic segmentation splits a write into multiple segments.

9.2 Encapsulation Overhead

The total overhead per encrypted tunnel packet is:

ComponentSize
PILS magic4 bytes
Sender Node ID4 bytes
GCM nonce12 bytes
Pilot header34 bytes
GCM auth tag16 bytes
Total overhead70 bytes

For plaintext tunnel packets (PILT), the overhead is 4 bytes (magic) + 34 bytes (header) = 38 bytes.

9.3 Effective Payload

Given a typical Internet path MTU of 1,500 bytes (Ethernet) and 8 bytes UDP header + 20 bytes IP header:

  • Available for Pilot: 1,500 - 28 = 1,472 bytes
  • Encrypted payload capacity: 1,472 - 70 = 1,402 bytes
  • Plaintext payload capacity: 1,472 - 38 = 1,434 bytes

The default MSS of 4,096 bytes exceeds the typical single-packet capacity. This means most full-MSS segments will be fragmented at the IP layer into 3 IP fragments. This is acceptable on most modern networks but may cause issues on paths with PMTU < 1,500 bytes or where IP fragmentation is blocked.

9.4 Recommendations

  • For Internet-facing deployments, an MSS of 1,400 bytes avoids IP fragmentation on virtually all paths.
  • For local or datacenter deployments, the default 4,096 MSS is safe (typical jumbo frame MTU is 9,000 bytes).
  • Implementations SHOULD provide a configurable MSS option.
  • Implementations SHOULD NOT set the DF (Don't Fragment) bit on UDP datagrams, allowing IP-layer fragmentation as a fallback.

10. Nonce Management

10.1 Tunnel Encryption Nonces

AES-256-GCM requires a unique 96-bit (12-byte) nonce for every encryption operation under the same key. Nonce reuse under the same key is catastrophic — it allows plaintext recovery and forgery.

10.2 Nonce Construction

Each tunnel session generates a nonce as follows:

[4-byte random prefix][8-byte monotonic counter]
  • Random prefix: 4 bytes generated from a cryptographically secure random source (crypto/rand) when the tunnel session is established. This prefix is unique per session with overwhelming probability.
  • Monotonic counter: 8-byte unsigned integer, starting at 0, incremented by 1 for each packet encrypted. The counter MUST NOT be reset within a session.

10.3 Session Lifecycle

A new tunnel session is established when:

  1. Two daemons perform an X25519 key exchange (PILK or PILA frame).
  2. Both sides derive a fresh AES-256-GCM key from the ECDH shared secret.
  3. Both sides generate a new random nonce prefix.

A new key exchange produces a new key and new nonce prefix. Old nonces cannot collide with new nonces because the key is different.

10.4 Counter Exhaustion

The 8-byte counter supports 2642^{64} packets per session. At 1 million packets per second, a single session would last over 584,000 years before counter exhaustion. Implementations MUST close the tunnel and re-key if the counter reaches 2642^{64} - 1. In practice, this condition is unreachable.

10.5 Application-Layer Nonces (Port 443)

The secure channel on port 443 uses a separate nonce scheme:

[4-byte role prefix][8-byte monotonic counter]
  • Role prefix: 0x00000001 for server, 0x00000002 for client. Fixed per role to prevent nonce collision between the two sides.
  • Counter: 8-byte unsigned integer starting at 0, incremented per encryption.

Each secure connection performs its own X25519 key exchange and HKDF-SHA256 key derivation (info: "pilot-secure-v1"), so nonce uniqueness is guaranteed per-key. The sender's nonce prefix (first 4 bytes) is used as GCM AAD on both sides.