Skip to content

Relays

a2a-serve relay runs an Agent Mesh relay that agents on laptops and in hosted sandboxes join from anywhere.

Note

Experimental. Agents join relays in builds with -tags agentmesh (see Networks). The relay commands work in every build.

A relay admits like a tailnet: everyone it admits can call everyone else it admits. Agents on it find each other by name, call each other from behind NAT, and get public URLs on the relay. The relay admits people who sign in with Google, limited to an email allowlist, and agents you invite.

Run a relay

A relay lives in a relay directory, like an agent: settings in .a2a/relay.yaml, state in .a2a/state/relay. A directory holds either an agent or a relay.

The relay needs an address that doesn't change, since agents and pairing tokens name it. relay run serves plain HTTP, so put it behind a reverse proxy or a tunnel that ends TLS, and give that address as --url:

mkdir ~/relay && cd ~/relay
a2a-serve relay run --url https://relay.example.com

The first run creates the relay directory, generates the relay's admin token, and downloads the agentmesh-one release this a2a-serve pins. It checks the download against a SHA-256 built into a2a-serve. Later runs need no flags:

a2a-serve relay run              # in the foreground
a2a-serve relay run --service    # as a launchd or systemd user service

For a demo, --tunnel cloudflare publishes the relay through a Cloudflare quick tunnel, with cloudflared from PATH. The tunnel's URL changes on every start, and agents have to join again then:

a2a-serve relay run --tunnel cloudflare

A relay keeps its data in SQLite in its relay directory, or in PostgreSQL with --database. relay run lists every flag.

a2a-serve relay status shows the relay's address, the versions it runs and how many agents are on it.

Let people sign in

People sign in to the relay from their agents with Google. The relay checks each account against --allow-email, with the rules agents use: an entry is an email, or *@example.com, which matches only accounts of that Google Workspace domain.

The relay signs people in with its own Google app, which you register once:

  1. In the Google Cloud console, create an OAuth client of type Web application.
  2. Add https://relay.example.com/a2a-serve/signin/callback as an authorized redirect URI, with your relay's URL. a2a-serve doctor in the relay directory prints it.
  3. Publish the app, as Internal for a Workspace domain or In production otherwise. Google expires the sign-ins of an app in testing after 7 days, and agents then have to sign in again.

Give the app and the allowlist to the relay:

a2a-serve relay run --url https://relay.example.com --allow-email '*@example.com' \
  --google-client-id <id> --google-client-secret env:GOOGLE_CLIENT_SECRET

Sign-in needs a URL that doesn't change, since the Google app names it, so it doesn't work with --tunnel cloudflare. --allow-email replaces the saved list, and --allow-email none turns sign-in off. Restart the relay to change them. The restart applies the new list to agents already on the relay, as Members and removal explains.

An agent joins without a token:

a2a-serve start opencode --private https://relay.example.com --public https://relay.example.com
→ sign in to the relay https://relay.example.com: https://relay.example.com/a2a-serve/signin/device?link=… (code BCDF-GHJK)

Open the link, sign in with Google, and approve the sign-in if the page shows the code the agent printed. On another device, open https://relay.example.com/a2a-serve/signin/device and type the code. The agent keeps a refresh token, so it doesn't need you again when it renews its credential or restarts. Each renewal checks the account with Google and against the allowlist again. An agent prints a new link when its sign-in no longer works: after you revoke the relay's access in your Google account, or after the relay gets a new admin token.

relay invite creates a single-use token that joins one agent:

a2a-serve relay invite ci-bot
invite ci-bot for https://relay.example.com, one agent, until 2026-11-06 09:00 UTC:
  mesh-bt-72fb0175…
on that agent: a2a-serve config set --private https://relay.example.com --public https://relay.example.com --relay-token -

The token is printed once. An unused invite expires after a day, or after --expires, up to a week. An invited agent keeps its place on the relay while it is away, even for days, until you remove it. An agent that stays away longer than 30 days needs a new invite.

Members and removal

a2a-serve relay members
AGENT     WHO               JOINED            SEEN              PEER
coder     anna@example.com  2026-11-04 10:12  2026-11-05 09:58  12D3KooWQx…
reviewer  invite            2026-11-04 10:15  2026-11-05 09:58  12D3KooWLm…

relay remove takes an agent name, a peer ID, or an email, which removes every agent of that person. The relay refuses the removed agent's next request, and the other agents drop it:

a2a-serve relay remove reviewer

Removing an agent that signed in bans the Google account behind it, so the person's other agents lose their place too, and they can't sign in again until the relay lets them back.

For people who sign in, --allow-email decides. When the relay starts, it removes the agents of people the list no longer admits, and lets back removed people it admits. So dropping a person from the list takes effect with the restart that applies it, and adding them back undoes a removal. To keep someone out, drop them from --allow-email too. A *@example.com entry admits everyone of that domain, so to keep out one person of it, list the others by email. Invite a removed invited agent again to let it back.

Manage a relay elsewhere

On the relay's machine, the relay commands reach the relay through its control socket. From anywhere else, give its URL and admin token. The admin token is in .a2a/state/relay/secrets under RELAY_ADMIN_TOKEN, unless you set it with --admin-token:

a2a-serve relay invite ci-bot --url https://relay.example.com --admin-token env:RELAY_ADMIN_TOKEN

Upgrades

Each a2a-serve release pins one Agent Mesh version. Agents and the relay should run the same one; a2a-serve doctor --online on an agent compares them.

After you install a newer a2a-serve on the relay's machine, run:

a2a-serve relay upgrade

It downloads the new agentmesh-one and restarts the relay. A newer Agent Mesh migrates the relay's database, and an older one can't use it again. So the upgrade first backs up a SQLite database to .a2a/state/relay/backups, and keeps the last three backups. A relay refuses to start with an older Agent Mesh than the one that last used its data. Back up a PostgreSQL database yourself before you upgrade.

Run in a container

Each release publishes the image ghcr.io/a2aproject/a2a-serve-relay. It runs a2a-serve relay run with the pinned agentmesh-one, in a relay directory that lasts as long as the container. Give it every setting as an argument, and keep its data in PostgreSQL:

docker run -p 8080:8080 -e RELAY_ADMIN_TOKEN -e RELAY_DATABASE ghcr.io/a2aproject/a2a-serve-relay \
  --listen=:8080 --url=https://relay.example.com \
  --admin-token=env:RELAY_ADMIN_TOKEN --database=env:RELAY_DATABASE

Run one container per relay. Agent Mesh runs a relay as one process, and two would form two meshes behind one URL. A guide for Cloud Run is planned.

The relay's sign-in keys derive from its admin token, so a container keeps its signed-in agents across restarts when it gets the same admin token.

Check a relay

In the relay directory, a2a-serve doctor checks the relay's settings, the permissions of its state, the downloaded agentmesh-one and the version of its data. With --online, it also checks that the relay answers at its URL, and that it serves its sign-in there.

The relay reads its own sign-in at its URL when it starts, as agentmesh-one does. So the machine that runs the relay has to reach the relay's URL, and relay run fails with a reason when it can't.