I wanted to give my Hermes Agent P2P capabilities, and develop a peer discovery server like SoulSeek. Nice side project for this week. I let Hermes install most of it itself, and then asked it to write down what it encountered, maybe it is of some help to others ?
AgentAnyCast is a P2P agent networking library built on top of libp2p and NATS for AI agents. There is currently no native Windows binary. The daemon (agentanycastd) only runs on Linux, which means WSL is required when running AgentAnyCast on Windows. This (first attempt at a) guide covers both the installation process and the pitfalls encountered during real-world deployment.
1. Preparing the WSL Environment
# Run from Windows Terminal or Git Bash
wsl -d Ubuntu -u root -- apt-get install -y -qq python3-venv sshpass
Pitfall #1: MSYS2 Path Translation
Git Bash automatically translates paths such as /opt into Windows paths like:
C:\Program Files\Git\opt\
To prevent this behavior, always use:
export MSYS2_ARG_CONV_EXCL="*"
Or prefix individual commands:
MSYS2_ARG_CONV_EXCL="*" wsl -d Ubuntu -u root -- <command>
Without this environment variable, absolute Linux paths passed to WSL may break unexpectedly.
2. Installing the AgentAnyCast SDK
# Create a virtual environment
# Avoid using /mnt/c/, as filesystem performance is significantly slower there.
wsl -d Ubuntu -u root -- python3 -m venv /opt/agentanycast-venv
# Install the SDK
wsl -d Ubuntu -u root -- /opt/agentanycast-venv/bin/pip install agentanycast
The SDK automatically downloads the appropriate Linux version of agentanycastd the first time a Node() instance is created.
Verify Installation
wsl -d Ubuntu -u root -- /opt/agentanycast-venv/bin/agentanycast --version
Expected output:
agentanycast, version 0.7.3 (SDK)
At the time of writing, the bundled daemon binary is version 0.7.2.
3. Starting Your First Node
Note : about RELAY :
relay multiaddress format: /ip4//tcp//p2p/
The Peer ID comes from the relay logs at startup: “peer_id”:”12D3KooW…”
Find it with : curl -s http:/(peer discovery server)/:8081/api/v1/agents | head
RELAY = “/ip4/(peer discovery server)/tcp/4001/p2p/12D3KooWxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”
Minimal working example (my_agent.py):
#!/opt/agentanycast-venv/bin/python3
import asyncio
import json
from agentanycast import Node, AgentCard, Skill
RELAY = "/ip4/144.172.102.63/tcp/4001/p2p/12D3KooWGMySnMqYHGxihHKJ68FgzGu87HknV8XTa7VNfcJftkNU"
card = AgentCard(
name="MyAgent",
description="My first agent",
skills=[Skill(id="echo", description="Echo input")]
)
async def main():
async with Node(card=card, relay=RELAY, home="/tmp/my-agent") as node:
print(json.dumps({"peer_id": node.peer_id}))
@node.on_task
async def handle(task):
text = task.messages[-1].parts[0].text
print(f"GOT: {text}")
await task.update_status("working") # REQUIRED
await task.complete(
artifacts=[
{
"parts": [
{"text": f"ECHO: {text}"}
]
}
]
)
stop = asyncio.Event()
await stop.wait()
asyncio.run(main())
Pitfall #2: update_status("working") Is Mandatory
Calling complete() directly produces:
FAILED_PRECONDITION: invalid transition: SUBMITTED -> COMPLETED
The daemon enforces the following state transition:
SUBMITTED → WORKING → COMPLETED
Always call:
await task.update_status("working")
before:
await task.complete(...)
4. Running Nodes in the Background
Avoid shell tricks such as & or nohup. Hermes’ terminal() helper already supports background execution:
from hermes_tools import terminal
terminal(
command='MSYS2_ARG_CONV_EXCL="*" wsl -d Ubuntu -u root -- /opt/agentanycast-venv/bin/python3 //mnt/c/nous/my_agent.py',
background=True,
notify_on_complete=True,
timeout=300
)
Pitfall #3: Missing Stdout When Running WSL in the Background
Background WSL processes do not always expose stdout through the process tool.
A more reliable approach is to write the peer ID to a file:
with open("/tmp/my-agent/peer_id.json", "w") as f:
json.dump({"peer_id": node.peer_id}, f)
5. Each Node Has Its Own Daemon
Every unique home= directory receives its own:
- Daemon binary (
bin/agentanycastd) - Ed25519 keypair (
key) - gRPC Unix socket (
daemon.sock) - Peer ID (derived from the key)
- Datastore (
data/) - Logs (
logs/)
Example:
/tmp/my-agent/
├── bin/agentanycastd
├── key
├── daemon.sock
├── data/
└── logs/
Important
If the daemon is killed, the socket file often remains behind.
The SDK then attempts to connect to a dead socket and fails with:
DaemonConnectionError: failed to connect to all addresses
Fix:
rm /tmp/my-agent/daemon.sock
rm -rf /tmp/my-agent/data
Alternatively, create a new home= directory.
Pitfall #4: The Socket Survives a Kill
A kill -9 terminates the daemon but leaves daemon.sock behind.
The SDK assumes the daemon is still alive and reconnect attempts fail until the socket is removed manually.
6. Node-to-Node Communication with send_task()
task = await node.send_task(
peer_id="12D3KooW...",
message={
"role": "user",
"parts": [
{"text": "Hello"}
]
}
)
result = await task.wait(timeout=30)
Tasks are delivered peer-to-peer through libp2p.
The receiving daemon logs:
incoming task registered
Pitfall #5: Task Reaches the Daemon but Not the Python Handler
The daemon successfully receives the task but does not forward it to the Python on_task handler unless the gRPC subscription (serve_forever()) is actively running.
In practice, this currently works reliably when:
- Client and server run inside the same Python process, or
- Both nodes are connected through the same daemon/bridge setup.
Cross-daemon task delivery appears to have a routing issue in version 0.7.2.
Pitfall #6: Empty Artifacts After task.wait()
The task status becomes COMPLETED, but:
result.artifacts == []
This appears to be a daemon bug in v0.7.2.
CompleteTask updates the task status correctly but does not return artifacts in the response payload.
7. Running a Relay / Bootstrap Node on a VPS
A central relay is required for:
- Bootstrap peer discovery
- Skill registry via gRPC (
:50052) - MCP StreamableHTTP (
:8080) - REST API (
:8081)
The relay uses the dedicated relay binary, not agentanycastd.
relay \
--listen /ip4/0.0.0.0/tcp/4001 \
--key /opt/relay-data/relay.key \
--registry-listen :50052 \
--mcp-listen :8080 \
--api-listen :8081 \
--registry-ttl 300s \
--log-level debug
Pitfall #7: discover() Does Not Work Through the Daemon
await node.discover("chat")
Returns:
anycast routing is not configured
Although the relay exposes a skill registry on port 50052, the daemon currently does not provide a Discover RPC implementation.
Instead, use the relay REST API:
curl http://<vps>:8081/api/v1/agents
8. Peer Discovery Server Integration
PeerDisco https://peerdisco.com (my recently developed peer discovery server) exposes an HTTP endpoint:
/mcp/agents
To make AgentAnyCast agents visible in PeerDisco:
PeerDisco
│
└── agntcy
│
▼
AgentAnyCast HTTP Bridge (:8888)
│
▼
Relay (:4001)
The bridge daemon must run separately on the VPS:
agentanycastd \
--bridge-listen :8888 \
--grpc-listen unix:///tmp/agntcy-bridge/daemon.sock \
--bootstrap-peers /ip4/<vps-ip>/tcp/4001/p2p/<relay-peer-id> \
--log-level info
Pitfall #8: Socket Directory Must Exist
Before starting the bridge:
mkdir -p /tmp/agntcy-bridge
Otherwise startup fails with:
ERROR: gRPC server error: bind: no such file or directory
PeerDisco’s health check should point to:
http://localhost:8888
9. SSH Access to the VPS from Windows
Install sshpass inside WSL:
wsl -d Ubuntu -u root -- apt-get install -y sshpass
Execute commands without an interactive password prompt:
sshpass -p "<password>" ssh -o StrictHostKeyChecking=no root@<vps-ip> "<command>"
Pitfall #9: Never Use pty=true on Windows
Using:
terminal(pty=True)
with SSH often triggers Git for Windows’ password popup dialog.
Instead, use sshpass through WSL.
Pitfall #10: sshpass Is Linux-Only
sshpass does not run natively on Windows.
Always execute it inside WSL rather than directly through Windows process execution.
10. Known Issues (v0.7.2 / v0.7.3)
| Issue | Impact | Workaround |
|---|---|---|
FAILED_PRECONDITION: SUBMITTED -> COMPLETED | Tasks cannot be completed | Call task.update_status("working") first |
Empty artifacts after task.wait() | Response payload is missing | Read task messages directly or use status checks |
discover() fails | Skill discovery unavailable | Query relay REST API (:8081/api/v1/agents) |
| Daemon socket survives process kill | Restart fails | Remove daemon.sock before restarting |
| Cross-daemon task routing | Tasks never reach Python handler | Run client and server in the same Python process |
11. Quick Checklist
- WSL Ubuntu installed with
python3-venvandsshpass MSYS2_ARG_CONV_EXCL="*"applied to every WSL command- Unique
home=directory for each node update_status("working")beforecomplete()serve_forever()orasync with Nodeactive for task reception- VPS relay uses the
relaybinary, notagentanycastd - PeerDisco bridge running via
agentanycastd --bridge-listen :8888 - Socket directory created with
mkdir -p peer_id.jsonwritten for debugging and process verification