AgentAnyCast P2P for Hermes Agent on WSL (Windows)

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)

IssueImpactWorkaround
FAILED_PRECONDITION: SUBMITTED -> COMPLETEDTasks cannot be completedCall task.update_status("working") first
Empty artifacts after task.wait()Response payload is missingRead task messages directly or use status checks
discover() failsSkill discovery unavailableQuery relay REST API (:8081/api/v1/agents)
Daemon socket survives process killRestart failsRemove daemon.sock before restarting
Cross-daemon task routingTasks never reach Python handlerRun client and server in the same Python process

11. Quick Checklist

  • WSL Ubuntu installed with python3-venv and sshpass
  • MSYS2_ARG_CONV_EXCL="*" applied to every WSL command
  • Unique home= directory for each node
  • update_status("working") before complete()
  • serve_forever() or async with Node active for task reception
  • VPS relay uses the relay binary, not agentanycastd
  • PeerDisco bridge running via agentanycastd --bridge-listen :8888
  • Socket directory created with mkdir -p
  • peer_id.json written for debugging and process verification

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top