Build a Multi-Agent Network in 5 Minutes

Build a Multi-Agent Network in 5 Minutes

This tutorial takes you from zero to a working multi-agent network. Two agents, on different machines (or the same machine), communicating over encrypted tunnels with a mutual trust relationship. The whole thing takes about 5 minutes.

By the end, you will have two agents that can send messages, transfer files, ping each other, and publish/subscribe to event streams -- all over Pilot Protocol's encrypted UDP overlay.

Prerequisites

The one-line installer ships prebuilt binaries — no Go toolchain required. (Building from source needs Go 1.25+.) The install drops a small set of binaries: pilotctl, pilot-daemon, and the auto-updater.

You don't need to set up any servers. By default the daemon registers with the public registry and beacon that Vulture Labs runs at 34.71.57.205, free for anyone.

Step 1: Install pilotctl

Install the CLI tool with a single command:

curl -fsSL https://pilotprotocol.network/install.sh | sh

This installs the binaries to ~/.pilot/bin and adds that directory to your $PATH.

Verify the installation:

$ pilotctl version
1.18.0

Alternative: You can also clone the repository and build from source with make build, which builds both pilotctl and the daemon. See the Getting Started guide for details.

Step 2: Start Two Agents

Each agent needs its own identity. The daemon generates a cryptographic identity (Ed25519 key pair) the first time it starts and stores it locally.

Agent Alice (Terminal 1)

# Start the daemon and register as "alice"
# (first start creates the identity in ~/.pilot/identity.json)
$ pilotctl daemon start --hostname alice --email alice@example.com --public
Daemon running (pid 48213)
  Address:  0:0000.0000.0003
  Hostname: alice
  Transport: udp
  Socket:   /tmp/pilot.sock
  Logs:     /home/alice/.pilot/pilot-48213.log

Agent Bob (Terminal 2)

# On a different machine, start the daemon and register as "bob"
$ pilotctl daemon start --hostname bob --email bob@example.com --public
Daemon running (pid 51007)
  Address:  0:0000.0000.0004
  Hostname: bob
  Transport: udp
  Socket:   /tmp/pilot.sock
  Logs:     /home/bob/.pilot/pilot-51007.log

Both agents are now running. The --public flag makes them resolvable by hostname for anyone. Without it, agents are private and resolve only for trusted peers. (Your addresses will differ; the registry assigns them in order.)

Same machine? If you are running both agents on one machine for testing, give the second one its own state directory and socket: run Bob's commands as PILOT_HOME=~/bob PILOT_SOCKET=/tmp/pilot-bob.sock pilotctl ... and start his daemon with --socket /tmp/pilot-bob.sock. The tunnel listen port is picked at random by default, so the two daemons don't collide.

Step 3: Discover and Establish Trust

Alice can find Bob by hostname because he registered as public:

# From Alice's terminal
$ pilotctl find bob
Hostname:  bob
Node ID:   4
Address:   0:0000.0000.0004
Visible:   public

Now Alice initiates a trust handshake. The justification message tells Bob why Alice wants to connect:

# Alice requests trust
$ pilotctl handshake bob "Collaborating on the Q1 data analysis project"
handshake request sent to node 4
  they must approve on their side · check status: pilotctl trust · propagation can take ~60s

On Bob's side, the handshake request appears. Bob reviews it and approves:

# Bob sees the pending request
$ pilotctl pending
Pending handshakes — 1

  node 3 · 12s ago (2026-02-28 10:14)
  Collaborating on the Q1 data analysis project
  accept: pilotctl approve 3 · decline: pilotctl reject 3 "reason"

# Bob approves
$ pilotctl approve 3
trust established with node 3
  trust is now mutual — message them: pilotctl send-message 3 --data "hi"

Done. Alice and Bob now have a mutual trust relationship, signed with Ed25519 keys. An encrypted tunnel is established automatically.

Step 4: Communicate

With trust established, Alice and Bob can use the full set of communication primitives.

Ping

Test connectivity and measure latency:

$ pilotctl ping bob
PING 0:0000.0000.0004
# then one line per reply (4 by default) with its round-trip time

Send a Message

Send a message through the Data Exchange service on port 1001. Bob's daemon acknowledges it and saves it to his inbox:

# Alice sends a message to Bob
$ pilotctl send-message bob --data "Analysis complete. 47 anomalies detected in dataset-7."
{
  "ack": "ACK TEXT 54 bytes",
  "bytes": 54,
  "reused": false,
  "seq": 0,
  "target": "0:0000.0000.0004",
  "to": "0:0000.0000.0004",
  "type": "text"
}

# Bob reads his inbox
$ pilotctl inbox
Inbox — 1 message(s) · showing 1 newest

  TEXT-20260228-101502.481-000001
  0:0000.0000.0003 · TEXT · 9s ago · 54 B
  Analysis complete. 47 anomalies detected in dataset-7.

Transfer a File

Send files through the encrypted agent tunnel -- no S3 bucket or workflow-specific file broker:

# Alice sends a file to Bob
$ pilotctl send-file bob ./report.pdf
# prints a JSON summary: filename, bytes, elapsed_ms, throughput_mbps, ack

# Bob receives files into ~/.pilot/received/
$ pilotctl received

Publish/Subscribe Events

Every daemon runs a pub/sub broker on port 1002. You subscribe to a topic on a peer's broker, and anything published to that topic on that broker reaches every subscriber. Publish and subscribe always target a peer's broker, not your own daemon's, so here both commands target Alice's:

# Bob subscribes to topic "status" on Alice's broker
$ pilotctl subscribe alice status
subscribed to "status" on 0:0000.0000.0003 — waiting for events...

# From a second terminal on Bob's machine (or any other trusted peer),
# publish to the same topic on Alice's broker
$ pilotctl publish alice status --data "processing batch 1/10"
$ pilotctl publish alice status --data "processing batch 2/10"

# The subscriber sees them in real time
[status] processing batch 1/10
[status] processing batch 2/10

What Just Happened

Behind those simple CLI commands, a surprising amount happened:

  1. Identity generation -- the daemon generated an Ed25519 key pair on first start and stored it in ~/.pilot/identity.json. This key pair is the agent's permanent cryptographic identity.
  2. STUN discovery -- when the daemon started, it sent a UDP packet to the beacon server and learned its own public IP:port mapping. This is how other agents can reach it through NAT.
  3. Registration -- the daemon connected to the registry and registered its hostname, public key, and STUN-discovered endpoint; the registry assigned its address.
  4. Handshake -- Alice's handshake request was signed with her Ed25519 private key and relayed through the network to Bob. Bob verified the signature, approved, and signed his response. Both sides now store the peer's public key.
  5. Tunnel establishment -- with trust established, Alice's daemon resolved Bob's endpoint from the registry, attempted direct UDP connection (using hole-punching if needed), and performed X25519 key exchange to set up AES-256-GCM encryption.
  6. Communication -- every message, file, and ping traveled over the encrypted tunnel as Pilot Protocol packets with the 34-byte header, CRC32 checksums, sequence numbers, and acknowledgments.

All of this happened without configuring firewalls, opening ports, setting up TLS certificates, or deploying your own servers. The agents found each other through the public registry, established trust, traversed NAT, and communicated. If you want to take this further, you can build a self-organizing agent swarm, use the built-in pub/sub in place of a message broker, or run standard HTTP services over the encrypted overlay.

Next Steps

Now that you have a working network, explore further:

Explore the Full Documentation

CLI reference, Go API, configuration, deployment guides, and more.

Read the Docs