A2A Support in the API Platform #2743
RakhithaRR
started this conversation in
Ideas
Replies: 1 comment 1 reply
|
Maintainer disclosure: I build Better Agent, a local agent control plane where durable identity, approvals, and restart recovery are backend-owned. The proxy shape is strong, but I would add acceptance criteria at the task lifecycle boundary, not only the request boundary:
I would also rate-limit by estimated work/cost as well as request count: one streaming SendMessage can be more expensive than hundreds of task reads. License note for the implementation reference: Better Agent is source-available and free for non-commercial use; commercial use requires separate permission. |
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
What problem are we solving, and why now?
Enterprises are deploying AI agents — their own and third-party — that increasingly call each other over the Agent2Agent (A2A) protocol to delegate tasks. Today a platform/API team has no consistent way to put these agent-to-agent interactions under governance: each agent re-implements its own auth, there is no central identity, authorization ("which agent may call which"), rate limiting, or cost control, and no end-to-end visibility across a multi-agent call chain. Teams want to govern A2A agents the way they already govern their other agent traffic, without modifying agent code.
A2A is now stable. A2A reached v1.0 under the Linux Foundation — its first stable release. The earlier 0.2.x/0.3.x versions changed rapidly and introduced breaking changes, making it risky to build durable gateway support against them; v1.0 provides a stable, vendor-neutral contract we can implement against with confidence. The AI Gateway governs enterprise agent traffic (LLM access, MCP tools), configured through AI Workspace; A2A is the agent-to-agent pillar and is currently the gap.
Who are we solving it for?
Primarily platform / API teams and agent developers in enterprises adopting multi-agent systems, and their security & compliance stakeholders who must audit and constrain agent interactions. Secondarily, the agents themselves (internal systems acting as callers) that need a stable, governed endpoint to reach other agents.
Solution
Introduce a new proxy type (
a2aproxy) in the gateway and the AI Workspace to onboard agents.flowchart LR subgraph CP["Control Plane — AI Workspace"] REG["A2A agent onboarding · policies<br/>security config · analytics"] end CLIENT["A2A clients /<br/>calling agents"] subgraph DP["Data Plane — AI Gateway"] GW["a2aproxy<br/>• Agent Card mediation<br/>• Transports: JSON-RPC / HTTP+JSON<br/>• Authentication<br/>• Rate limiting<br/>• Guardrails<br/>• Analytics & observability"] end UP["Upstream A2A agents<br/>(A2A v1.x)"] REG <-->|"config · sync"| GW CLIENT -- "A2A request<br/>(JSON-RPC / HTTP+JSON, SSE)" --> GW GW -- "forward request" --> UPThe platform will support the following capabilities:
securityRequirements, skills, etc. based on the user requirements.User Stories
As an agent developer, I want to publish a rewritten Agent Card at the gateway's own well-known URL, so that clients discover the gateway endpoint and all traffic flows through governance.
Acceptance Criteria
/.well-known/agent-card.jsonrelative to the base path of the A2A proxy created, with the URLs rewritten to the gateway address.As an A2A client developer, I want to call the agent over either the JSON-RPC or the HTTP+JSON binding, so that I can use whichever transport my A2A SDK supports.
Acceptance Criteria
/path as well as specific REST endpoints based on the transport protocol used.As an agent developer, I want the gateway to authenticate every caller against a declared scheme before forwarding, so that only authorized clients can reach an agent.
Acceptance Criteria
As an agent developer, I want the public Agent Card to expose only non-sensitive metadata, so that discovery does not leak internal endpoints or sensitive skills.
Acceptance Criteria
As a security & compliance officer, I want the gateway to verify and optionally re-sign Agent Card signatures, so that clients can trust the card was not tampered with.
Acceptance Criteria
As an agent developer, I want to apply rate limits, so that expensive A2A operations can be constrained independently of cheap ones.
Acceptance Criteria
SendMessagevstasks/get) and enforced independently.As an agent developer, I want end-to-end traces and per-agent metrics for A2A traffic, so that I can monitor and troubleshoot multi-agent interactions.
Acceptance Criteria
gen_ai.agent.idand the A2A operation.Challenges
allOforanyOfsort of scenarios. Need to check whether we can support such scenarios through our policy approach.Card Mediation: acquisition, computation, and curation
The gateway serves a rewritten Agent Card, not the upstream one —
supportedInterfaces[].urlrewritten to the gateway,securitySchemes/securityRequirementsderived from the attached frontend-auth policy, upstream security stripped, and (optionally) re-signed with the gateway key + published JWKS.Acquisition: live proxy-and-rewrite vs. snapshot-and-serve
Two fundamentally different ways to obtain and maintain the served card.
Option 1 — Live proxy-and-rewrite. The gateway proxies the well-known path to the upstream at request time and rewrites the card's URLs on the fly (gateway address from
X-Forwarded-*).signCardforces a full JWS operation (canonicalization + signature) on every discovery request, a hot path clients hit before every session.Option 2 — Snapshot-and-serve at deploy time (recommended). The card is fetched once, snapshotted into the deployed config, and the controller computes the served card at deploy, serving it from a local, non-forwarding (synth) route at
/{context}/.well-known/agent-card.json(the same pattern used for MCP's OAuth Protected Resource Metadata).How the served card is computed (Option 2)
The pushed
kind: A2aspec carries inputs + curation, not a frozen card:agentCardUrl/ snapshotpoliciessecuritySchemes/securityRequirements+ skill visibilitycardOverridessignCardThe controller computes the served card at deploy — identically for AI Workspace and gateway-only:
Why the controller computes it (not AI Workspace): URL rewriting needs the runtime gateway address and re-signing needs the gateway's signing key — neither exists in the control plane. Since those steps are inherently gateway-side, all derivation lives in the controller. AI Workspace's role is to author the spec and preview; the controller turns the spec into the served card. This keeps both authoring modes behaviorally identical.
Authoring modes
Recommended —
agentCardUrl(AI Workspace auto-snapshots; gateway-only fetches at deploy). The user suppliesagentCardUrl; the controller derives everything a hand-authored card would spell out — the rewrittensupportedInterfaces[].url(fromcontext+ gateway host) andsecuritySchemes/securityRequirements(from thejwt-authpolicy). NoagentCardblock needed in the common case.Gateway-only override — inline
agentCard(a hand-authored snapshot). For air-gapped / no-egress deployments where the controller can't fetch upstream, the operator supplies the card inline; the controller still URL-rewrites and signs it. In AI Workspace this inline card is produced automatically as a snapshot (stored with the curation overrides) — the operator never hand-writes it.Note the inline snapshot carries source fields only — no
securitySchemes/securityRequirements(derived from thejwt-authpolicy) and nosignatures(the controller re-signs).Snapshot & drift
name,description,skills,capabilities,supportedInterfaces. Not upstreamsecuritySchemes/securityRequirements(derived from policies) and not upstreamsignatures(invalidated by the rewrite).Card editing & devportal publishing (discovery)
Editing the card in AI Workspace is valuable beyond mediation: the controller-generated card is a baseline, and the workspace lets operators curate it — descriptions, tags, surfaced skills, public-vs-extended split, docs links. The curated card then has two destinations:
This directly addresses the registry gap (A2A has no standardized registry yet, a2aproject/A2A#741) — the devportal becomes the discovery surface for governed A2A agents, exactly as it already is for REST/MCP/LLM APIs. Curation feeds both destinations from one edited card, published from the same versioned card revision to keep them consistent.
Proposal: acquire the card by snapshot-and-serve at deploy (not live proxy-and-rewrite — signing and curation both favor it); the controller computes the served card from the pushed spec (
agentCardUrl/snapshot +policies+cardOverrides+signCard), serving it from a synth well-known route and signing once at deploy. AI Workspace adds authoring + preview + curation on top, and publishes the curated card to both the gateway and the devportal. Upstream-auth secret note: the backend credential is always manual in both modes — the card reveals only which scheme, never the secret.Transports: JSON-RPC methods ↔ HTTP+JSON paths (v1.0)
Every A2A operation maps onto both bindings the gateway must support. In JSON-RPC everything hits
POST /and the operation is selected by themethodfield; in HTTP+JSON the operation is the HTTP verb + path. These are the routes the gateway classifies, authenticates, and rate-limits.POST /)SendMessagePOST /message:sendSendStreamingMessagePOST /message:streamSubscribeToTaskGETorPOST /tasks/{id}:subscribeGetTaskGET /tasks/{id}ListTasksGET /tasksCancelTaskPOST /tasks/{id}:cancelGetExtendedAgentCardGET /extendedAgentCardGET /.well-known/agent-card.json(The push-notification / webhook config operations —
CreateTaskPushNotificationConfigetc. /…/tasks/{id}/pushNotificationConfigs— are deferred along with the gateway-mediated webhook story and omitted here.)How the two bindings differ (same operations, different envelope):
POST /; the operation is chosen by themethodfield (PascalCase), andparamscarries the request message. Responses are wrapped in the JSON-RPC envelope ({"jsonrpc","id","result"}), withresultholding the typed payload (e.g.SendMessageResponse={"task": …}).POSTs the body is the bare request message (no JSON-RPC wrapper) and the response is the bare object.{id}is the task id.SendStreamingMessage,SubscribeToTask) returntext/event-stream(SSE) in both bindings; each frame is aStreamResponseenvelope ({"statusUpdate": …}/{"artifactUpdate": …}/{"task": …}), wrapped in a JSON-RPCresultfor JSON-RPC, or emitted bare for REST.All reactions