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:
- In the Google Cloud console, create an OAuth client of type Web application.
- Add
https://relay.example.com/a2a-serve/signin/callbackas an authorized redirect URI, with your relay's URL.a2a-serve doctorin the relay directory prints it. - 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.