> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: A2A federation discovery and peer tasks

> Decode the four A2A (Agent2Agent) federation failure classes — a refused discovery toggle (A2A_DISCOVERY_DISABLED), an inbound envelope rejected on the HMAC signature, an outbound task failing with A2A_PEER_ERROR, and a peer registration refused by the public-FQDN check — with the fix for each and a decision checklist.

# Troubleshooting: A2A federation discovery and peer tasks

Four failure classes cover every A2A federation problem a tenant hits: the
public AgentCard will not serve, an inbound task envelope is rejected before
a skill ever runs, an outbound delegation fails at the peer, or a peer
registration is refused because the URL you pasted is not a public FQDN. Each
class has its own gate with its own fix — this runbook matches them.

The federation surface itself (discovery modes, the two transports, the
signing schemes) is documented in the
[A2A federation model](/concepts/a2a-federation-model) concept page and the
[A2A federation](/agents/a2a-federation) feature page; the registered error
codes sit in the [error reference](/reference/error-codes). This page is the
fix path for each gate.

## Symptom class 1 — Register/discovery refused (`A2A_DISCOVERY_DISABLED`)

The dashboard's **Agents → {agent} → A2A** tab refuses to load the agent's
card with a `422 A2A_DISCOVERY_DISABLED`, or a peer reports that the public
`/.well-known/agent-card.json` URL returns `404` for a perfectly valid agent
id. Both are the same gate: the agent's `a2a_discovery_mode` is `disabled`
(the default) or `tenant`.

| Discovery mode       | Response                                                              | What blocks it                                                                                              |
| -------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `disabled` (default) | public route `404`; dashboard card route `422 A2A_DISCOVERY_DISABLED` | The agent serves no public AgentCard and the toggle was never flipped.                                      |
| `tenant`             | public route `404`; dashboard card route serves                       | The card serves to authenticated dashboard sessions only — a peer fetching the public URL still gets `404`. |

The default is deliberately closed: serving a card to the open internet
would expose the agent's skill catalog to every crawler, and the `404` on
the public route is designed to leak nothing — it looks identical to "agent
does not exist."

**Fix for operators:** open **Agents → {agent} → A2A** in the dashboard and
set the agent's discovery toggle to `public` (or `tenant` when you hand the
share URL over an authenticated channel). The same toggle lives on
`PUT /agents/{agentId}` as `a2a_discovery_mode` — see [A2A federation → discovery
modes](/agents/a2a-federation#discovery-modes).

## Symptom class 2 — Inbound task envelope rejected on the signature (401)

A peer POSTs to `/a2a` or `/a2a/tasks` and gets a `401` with a fixed,
operator-safe message. The exact discriminant (missing / malformed / expired
/ mismatched signature) is never leaked to the peer by design.

The envelope must carry the Stripe/Svix-style header:

```
X-A2A-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256(secret, "<ts>.<raw_body>")>
```

The most common causes, in the order to check them:

1. **Wrong secret** — the peer signs with an old HMAC key (after a rotation)
   or a key minted for a different tenant.
2. **Signed the wrong bytes** — the payload is `${ts}.${raw_body}` exactly as
   sent: one extra newline or a re-serialized body whose wire bytes differ
   from the bytes you hashed fails the check.
3. **Stale timestamp** — the signed `t=` must sit within the ±60-second
   replay window. A clock skew beyond 60 s on the peer's infra fails as
   "mismatched," not as "expired."
4. **Missing header entirely** — a Python `requests` / `urllib` client that
   set the header on one branch but not another behaves as unsigned.

**Fix:** whoever owns the signing key (you or your peer) rotates it, re-share
it over a side channel, and re-sign. Until then every task retries the same
gate; a busy 401 loop only burns your own logs. (For JSON-RPC peers: the
401 rides the JSON-RPC `INVALID_REQUEST` shape, but the cause list is
identical.)

## Symptom class 3 — Outbound task fails with `A2A_PEER_ERROR` (502)

Orbit forwards the task to the peer and the downstream agent errors it:
`502 A2A_PEER_ERROR`. The fix lives on the peer's side, not yours.

The envelope the peer returned carries the cause — read it before doing
anything else:

* **Peer-side agent errored the task.** Their run log (or, on another
  Orbit tenant, their dashboard A2A tab) holds the failing turn.
* **The peer's AgentCard became unreachable.** Re-fetch it: the cached
  catalog refreshed without a usable endpoint.
* **Your peer registry drifted.** A URL that pointed at one agent now
  resolves to none — the peer row still looks healthy.

Do not retry in a loop. A peer that rejects on every request answers the
same gate per retry, and hammering a failing endpoint earns the peer's
429 rather than your task. Fix the peer, then dispatch once.

## Symptom class 4 — Peer registration refused (public-FQDN check)

`POST /a2a/peers` returns `400 VALIDATION_ERROR` (or the route's SSRF
gate) when the passed `peer_url` is a `localhost` address, a private
IP/CGN range, a link-local cloud-metadata host, or any internal-only
suffix. The guard runs on the URL itself, not the tenant's posture —
no tenant toggle can paste an on-network peer.

**Fix:** publish the peer's AgentCard at a public FQDN over HTTPS and
re-register. A peer served from inside a VPC cannot federate across trust
boundaries; place the public card behind a proxy or on a public staging URL.

## Decision checklist

Run the checks in this order and stop at the first one that answers:

1. **Confirm the discovery toggle.** `Agents → {agent} → A2A`: is the
   agent's mode `public` (inbound-class problems) or is the peer in your
   registry at all (outbound-class problems)? Dashboard-fetch `422` and
   public-fetch `404` both answer "no."
2. **Confirm the peer's signing key.** If the task is inbound-rejected,
   re-share the HMAC secret and verify the peer signs
   `${ts}.${raw_exact_bytes}` within ±60 s.
3. **Read the peer-error envelope.** Pull the `id` of the outbound task on
   `GET /a2a/tasks` (dashboard) or `tasks/get` (REST/JSON-RPC), and read
   the peer's returned error.
4. **Inspect the agent-side run.** Open the run in
   [Live monitor](/agents/live-monitor) — the same turn the peer's engine
   executed shows where the failure actually sat.

## What NOT to do

* **Do not leave discovery open on a dev tenant.** A public toggle on a
  scratch tenant gives every crawler your agent's skill catalog. Flip it
  off when you are done.
* **Do not try to bypass the signature check.** Once a signing key is
  registered, every unsigned envelope must be rejected — the only fail-open
  boundary is "no key registered at all," per the
  [concept page](/concepts/a2a-federation-model). Do not ask the peer to
  send unsigned.
* **Do not re-task in a tight loop.** Hammer a failing peer and you trade
  your retry for its `429`. Use backoff and fix the peer first.

## Full-error sample (paste-ready)

Copy this envelope into a ticket so support gets the stable anchors:

* `request_id` — the id you key on
* `catalog_p1` — the route+agent+peer triple
* `direction` — inbound vs outbound

```json theme={null}
{
  "error": {
    "code": "A2A_PEER_ERROR",
    "message": "Peer task delegation failed.",
    "status": 502
  },
  "meta": {
    "request_id": "req_01J9T4QH2C",
    "timestamp": "2026-09-14T08:11:05.302Z"
  }
}
```

For an inbound-signature failure, also include the `X-A2A-Signature`
header's `t=` timestamp so the ±60-second window can be checked.

## When to escalate

Escalate to support only after the checklist empties. An inbound-side
problem needs **both tenants side-by-side** — the agent owner's
discovery-mode state and the peer's signed-envelope sample. For an
outbound-side problem, include the peer's task id plus your own
`request_id` from `meta`; without both, one side can only say "the other
tenant's gate fired."

## See also

* [A2A federation →](/agents/a2a-federation) — endpoint-by-endpoint
  reference: discovery modes, the two transports, peer registry.
* [A2A federation model →](/concepts/a2a-federation-model) — the concept
  page this runbook assumes.
* [Troubleshooting: agent runtime errors →](/troubleshooting/agent-errors) — sibling
  runbook for the run-time failure codes a federated task can also route.
* [Error reference](/reference/error-codes) — the full registered code
  catalog, A2A subsection.
