DEV Community

Philip Stayetski
Philip Stayetski

Posted on

Test Agent-to-Agent Connectivity Locally Before Deploying: Two Daemons, One Host

You wrote two agents. Unit tests pass, the tool-call mocks are beautiful, and the first integration test — both agents in one process — works too. Then you deploy, and agent A cannot reach agent B. Not a logic bug. A connectivity bug: the handshake times out, the address doesn't resolve, or the message lands on a channel the peer never approved.

The fix is boring and effective: test agent-to-agent connectivity locally before deploying. Run two daemon processes on one host, give each a real identity, and exercise the same failure modes you'll hit at distance — restarts, address changes, trust decisions, lossy links. If the pair survives that loop, deploying is a formality.

Here's the dev loop I use, and the exact commands.

What "agent-to-agent connectivity" actually means

Before you test anything, name the layers. Four things have to be true for agent A to talk to agent B:

  1. Addressing — A can name B. A name that survives restarts and IP changes, not a socket that dies with the process.
  2. Transport — A's bytes actually reach B. Through NAT, through firewalls, without a static IP on either side.
  3. Trust — B accepts messages from A. Explicit, per-peer, mutual — not "everything on the VPN is trusted."
  4. Discovery — A can find out that B exists at all.

Unit tests mock all four. Integration tests usually run both agents inside one process, which makes them impossible to break — localhost always answers. Neither tells you anything about production.

The two-process dev loop

The core move: stop testing agents and start testing the network between them. Two daemon processes on one host, each with its own identity and address, talking over the same transport they'll use in production.

Pilot Protocol is an open-source overlay network built for exactly this shape, and its daemon is the identity layer, so the harness is a two-minute setup. Start node one and node two with separate identity files:

# node one
pilotctl daemon start --identity ~/.pilot/identity-a.json --hostname agent-a

# node two
pilotctl daemon start --identity ~/.pilot/identity-b.json --hostname agent-b
Enter fullscreen mode Exit fullscreen mode

Each daemon registers a permanent virtual address that survives restarts and IP changes. That's the property you're about to test.

Simulate what goes wrong at distance

Localhost hides everything. Two daemons with real identities don't. Walk the failure modes in order:

1. Restart one daemon. Kill node B, start it again. Does its address survive? If a daemon re-registers with a new address on every boot, peers can't find it, and your "distributed" system breaks on a deploy. With Pilot, the identity file persists, so the address should come back the same:

pilotctl info          # note the address
# restart the daemon...
pilotctl info          # same address? good.
Enter fullscreen mode Exit fullscreen mode

2. Replace the identity. Start node B with a new identity file. The peer should no longer recognize it — trust is tied to identity, not to the IP it came from. If your test harness happily accepts a stranger, your trust model is fake.

3. Send before approval. Try to message node B from A before any handshake. It should fail. pilotctl pending should show the request waiting, and nothing should flow until both sides approve. Membership and trust are decoupled: "joined the network" must never mean "trusted by everyone."

pilotctl pending       # incoming requests waiting on you
pilotctl approve <node-id>
Enter fullscreen mode Exit fullscreen mode

4. Round-trip both directions. Trust is mutual, so test it both ways:

pilotctl ping <peer-address>
pilotctl send-message <peer-address> --data 'hello from agent-a' --wait
Enter fullscreen mode Exit fullscreen mode

5. Move one node behind a different network. A container with its own network namespace, a hotspot, a cloud VM — anything that puts real NAT in the path. This is the step localhost can't fake, and it's where most A2A setups die. Pilot handles it with STUN plus hole-punching and a relay fallback (the beacon), so the same commands keep working when the direct path doesn't.

A concrete test script

Here's the whole loop as a script you can run before every deploy:

#!/usr/bin/env bash
set -e

# 1. identity survives restart
ADDR_BEFORE=$(pilotctl info --json | jq -r .data.address)
pilotctl daemon stop && pilotctl daemon start
ADDR_AFTER=$(pilotctl info --json | jq -r .data.address)
[ "$ADDR_BEFORE" = "$ADDR_AFTER" ] || { echo "FAIL: address changed"; exit 1; }

# 2. unapproved peer cannot send
pilotctl pending | grep -q . && echo "note: pending requests exist"

# 3. message round-trips
pilotctl ping agent-b
pilotctl send-message agent-b --data 'ping' --wait

echo "PASS: agent-to-agent connectivity OK"
Enter fullscreen mode Exit fullscreen mode

Run it, fix what breaks, run it again. That's the loop.

Test agent-to-agent connectivity locally before deploying — the checklist

If you take nothing else from this post, use this list:

  • [ ] The address survives a daemon restart.
  • [ ] A message round-trips over the tunnel in both directions.
  • [ ] An unapproved peer cannot deliver messages.
  • [ ] Approval flips delivery on, with no code changes.
  • [ ] A fresh identity is rejected by the peer.
  • [ ] Discovery works: A can find B by name, not by hardcoded IP.
  • [ ] The same commands pass with real NAT in the path.

Every item is a thing that breaks in production and costs an hour to debug. Every item is testable on one machine.

Why the daemon shape matters

The reason two daemons on one host is the right harness, rather than a mock, is that you're testing the actual artifacts you'll deploy: the process, its identity, its trust decisions, its transport. When the pair works locally — including a restart and a NAT hop — the remaining risk between you and production is configuration, not architecture.

Pilot Protocol is one honestly-fitting option for this loop: it's open source (AGPL-3.0, Go, no external dependencies), gives every agent a permanent virtual address, encrypts tunnels with X25519 key exchange and AES-GCM, and makes trust an explicit per-peer handshake. The Pilot Protocol docs cover the addressing, transport, and trust model in full. It also has an app store of agent-native tools you can install with a single command once your nodes are talking — but that's a follow-up post.

Get started and run the loop yourself:

curl -fsSL https://pilotprotocol.network/install.sh | sh
Enter fullscreen mode Exit fullscreen mode

Two daemons, one host, a few minutes. Cheaper than the first production outage.

Top comments (0)