AgentSteadDocs

A2A Gateway

Let any A2A-compatible AI framework — including NVIDIA NeMo Agent Toolkit — discover and invoke your AgentStead agents over Tailscale.

The A2A Gateway is a small sidecar that runs alongside each of your agents. It speaks the Linux Foundation Agent-to-Agent (A2A) protocol — an open standard for AI agents to discover each other's capabilities and exchange tasks — so that external frameworks like NVIDIA NeMo Agent Toolkit can use your AgentStead agents as tools in their own workflows.

What it does

When an agent has Tailscale connected, the platform automatically starts an A2A gateway alongside it. The gateway:

  • Advertises the agent via a standard Agent Card at /.well-known/agent-card.json — this is how A2A clients discover what the agent can do
  • Accepts tasks sent by any A2A-compatible client (NeMo, another agent, a custom client) using the message/send JSON-RPC method
  • Forwards tasks to the agent runtime and streams the response back
  • Lives on your tailnet at http://<tailscale-ip>:8080 — not exposed to the public internet

All communication goes over Tailscale's encrypted WireGuard tunnel, so no credentials or API keys flow through the public internet.

Prerequisites

  • The agent must be running the OpenFang runtime
  • Tailscale must be connected on the agent (see Networking & SSH)
  • The client invoking the agent (NeMo, etc.) must be on the same Tailscale network

Support for OpenClaw, NanoBot, ZeroClaw, and NullClaw runtimes is in development. Those agents will start the gateway sidecar but return a not yet supported response until their adapters are ready.

Finding your A2A endpoints

Once an agent has a Tailscale IP, open Networking in the sidebar, select your workspace, and scroll down to the A2A Mesh section. It lists every agent in the workspace that has a gateway running, with its tailnet URL.

You can also fetch the list programmatically:

GET /api/steads/{steadId}/a2a/agents
[
  {
    "agentId": "clx...",
    "name": "researcher",
    "agentType": "OPENFANG",
    "tailscaleIp": "100.82.67.50",
    "a2aUrl": "http://100.82.67.50:8080",
    "cardUrl": "http://100.82.67.50:8080/.well-known/agent-card.json",
    "status": "ONLINE"
  }
]

Connecting NeMo Agent Toolkit

The fastest way is the Copy NeMo config button in the A2A Mesh panel. It generates a ready-to-paste function_groups block for your NeMo workflow config.

Example output for a workspace with two agents:

function_groups:
  researcher:
    _type: a2a_client
    url: http://100.82.67.50:8080
    task_timeout: 60
  coder:
    _type: a2a_client
    url: http://100.94.12.33:8080
    task_timeout: 60

Paste this into your NeMo workflow YAML alongside your workflow: block:

function_groups:
  researcher:
    _type: a2a_client
    url: http://100.82.67.50:8080
    task_timeout: 60

workflow:
  _type: per_user_react_agent
  tool_names:
    - researcher
  llm_name: nim_llm

NeMo will automatically fetch the Agent Card, discover the agent's skills, and present them as callable tools to the LLM.

A2A clients in NeMo are per-user function groups. Your workflow must use per_user_react_agent or another per-user workflow type — react_agent (shared) cannot use A2A clients.

You can also generate the config via API:

GET /api/steads/{steadId}/a2a/nemo-config

Returns text/plain YAML ready to paste.

Using the gateway directly

You can call the gateway from any HTTP client — no NeMo required.

Discover the agent card

curl http://<tailscale-ip>:8080/.well-known/agent-card.json | jq .
{
  "name": "researcher",
  "description": "OpenFang agent",
  "url": "http://100.82.67.50:8080",
  "protocolVersion": "0.3.0",
  "capabilities": { "streaming": false },
  "skills": [...]
}

Send a task

curl -X POST http://<tailscale-ip>:8080 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "message/send",
    "params": {
      "message": {
        "role": "user",
        "parts": [{ "type": "text", "text": "Summarise the latest AI research papers" }]
      }
    }
  }'

The response contains the agent's reply as an A2A Message object.

Health check

curl http://<tailscale-ip>:8080/healthz
# → 200 OK

Gateway endpoints reference

EndpointMethodDescription
/.well-known/agent-card.jsonGETA2A Agent Card — capability discovery
/POSTA2A message/send JSON-RPC endpoint
/healthzGETHealth check — returns 200 OK

Runtime support

RuntimeA2A GatewayStatus
OpenFang✅ Full supportLive
OpenClaw🔄 Stub (graceful error)Coming soon
NanoBot🔄 Stub (graceful error)Coming soon
ZeroClaw🔄 Stub (graceful error)Coming soon
NullClaw🔄 Stub (graceful error)Coming soon

A stub gateway means the sidecar starts cleanly and the Agent Card is served, but sending a task returns a not yet supported error rather than crashing or looping.

How it works

The gateway is a compiled Go binary built with the official a2a-go SDK. It runs as an extra container in the agent's Docker Compose stack.

Docker Compose stack (per agent)
├── tailscale      ← Tailscale sidecar (100.x.x.x IP)
├── runtime        ← Agent runtime (OpenFang, OpenClaw, etc.)
├── a2a-gateway    ← A2A gateway (shares tailscale network namespace)
└── browser / ssh  ← Optional UI & access containers

The gateway shares the Tailscale container's network namespace, which means:

  • It inherits the 100.x.x.x tailnet IP
  • It reaches the runtime at http://runtime:<port> on the stack's internal Docker network
  • It is not reachable from the public internet — only from your tailnet

When a task arrives at the gateway:

  1. Extracts the text from the A2A message/send request
  2. Translates it into the runtime's native internal API call
  3. Returns the response as an A2A Message artifact

Troubleshooting