Skip to main content
The a2a_agents field lets an assistant hand a question to a remote agent that speaks the A2A (Agent2Agent) protocol. You register the agent’s URL and credentials once; the skills it advertises on its agent card become tools the assistant can call. This is the point of configuring agents rather than tools. When the remote agent gains, renames, or drops a skill, the assistant follows without a change to your assistant configuration — on the first conversation that starts after the five-minute card cache expires. It is the same bargain MCP makes with list_tools.
Beta: A2A agent support is in beta. The a2a_agents configuration described here may change before general availability.
In this guide, you will learn:
  • How an agent card becomes tools
  • How to configure an agent, authenticate to it, and pass secrets
  • When to make a delegation asynchronous
  • Which limits apply and what happens when an agent is unreachable

How it works

  1. You configure an agent — a name, a URL, and any headers it needs.
  2. The card is fetched at conversation start. /.well-known/agent-card.json is appended to the URL’s path, unless that path already ends in .json, in which case the URL is used as-is.
  3. Each advertised skill becomes one tool, named a2a_<name>_<skill_id>, described by the skill’s own description from the card.
  4. The model calls a tool with a single free-text message. A2A agents route on the message itself, so there is no per-skill parameter schema to mirror.
  5. The agent’s reply comes back as the tool result, and the assistant speaks it.
Cards are cached for five minutes, so only the first conversation to use an agent pays the round trip.

Configure an agent

Add a2a_agents when you create or update an assistant:
With that configuration, the card at https://agents.example.com/billing/.well-known/agent-card.json is fetched at conversation start. A card advertising skills refund_status and invoice_lookup gives the assistant two tools: a2a_billing_agent_refund_status and a2a_billing_agent_invoice_lookup.

Fields

URL rules

The URL must use http:// or https:// and resolve to a host on the public internet. Internal destinations — localhost, private and reserved IP ranges, .local domains — are rejected when the assistant is saved, as are hostnames written in abbreviated, hexadecimal, or octal numeric form. The hostname may not contain a {{...}} placeholder: the host a placeholder resolves to cannot be checked at save time. Placeholders in the path are fine, because the host stays fixed.

Authenticate to the agent

Header values are stored exactly as written and resolved per conversation. A value can be:
  • a literal, such as Bearer abc123
  • a {{dynamic_variable}}, resolved from the conversation’s dynamic variables
  • an {{#integration_secret}}identifier{{/integration_secret}} section, resolved from a stored integration secret
Prefer an integration secret for anything long-lived: the secret itself never enters the assistant configuration, so it is never returned when you read the assistant back. A2A headers do not accept the encrypted {{variable | encryption_secret_ref}} form used for per-caller credentials. That reference is resolved only in an MCP server’s api_key_ref and in tool webhook header values; using it here is rejected with a 422 when you save the assistant. For a value that has to vary by caller, return it as a plain {{dynamic_variable}} from the dynamic variables webhook — bearing in mind that plain dynamic variables are not encrypted and are also interpolated into instructions and messages.
A header whose name or value resolves to an empty string is dropped for that conversation. If an agent starts returning 401, check that every placeholder in its headers actually resolves.

Synchronous and asynchronous delegation

By default a delegation is synchronous: the turn waits for the remote agent, up to timeout_ms. If the agent answers with a task that is still running, it is polled every poll_interval_ms until it settles, and cancelled if the budget runs out. Set "async": true when the agent is slow enough that waiting would leave dead air. The model is told the request is on its way and the turn completes immediately; when the answer lands it is added to the conversation without interrupting the caller, and the assistant picks it up on its next turn.
Use synchronous delegation when the answer is the caller’s next sentence, and asynchronous delegation when the caller can keep talking without it. For a synchronous agent that takes a few seconds, messages fills the silence the same way webhook filler messages do:
Filler messages are not used when async is true — there is no silence to fill.

Tool names

Derived names are a2a_<name>_<skill_id>, truncated at 64 characters. Characters outside [A-Za-z0-9_] in the agent name are replaced with _ before the name is built, so billing agent and billing_agent produce the same tool names and cannot both be configured on one assistant — saving the second is rejected. The 43-character limit on name exists for the same reason: a longer name would consume the whole 64-character budget, leaving every skill on that agent with the same truncated tool name. If a derived name still collides with another tool the assistant already has, a _2, _3, … suffix is added so that dispatch stays unambiguous.

Limits

These are applied when the conversation starts, not when the assistant is saved. Anything past them is dropped silently, so keep configurations inside them: Agents are read in the order you configure them and the limits are applied in that order, so put the agents that matter most first.

When an agent is unavailable

A card that cannot be fetched, times out, or advertises no skills yields no tools. The assistant loses that capability for the conversation; it does not lose the call. If the model is asked to do something only that agent could do, it will say it cannot rather than fail. Once a tool exists and a call to it fails, the failure is reported to the model as ordinary text — “The agent did not answer in time”, “The agent could not be reached” — so the assistant can acknowledge it and move on. That failure mode is quiet by design, which is why a malformed configuration is rejected at save time instead: a typo you can see in a 422 is better than a tool that silently never appears.

Troubleshooting


API reference