a2aproject / a2aproject/A2A

[Feat]: Introduce normative A2A Client API specification with code generation support

Open
#2,023 2 comments 1 reaction 0 assignees View on GitHub
Dominant language
Shell
Stars
25.7k
Forks
2.6k
Avg merge
3d 6h
Merged PRs (30d)
16

Description

## Is your feature request related to a problem? Please describe.

The A2A specification currently defines a single `A2AService` in `a2a.proto`. This service describes operations that an individual agent *server* exposes. Client libraries are generated directly from that service definition — meaning the "client API" is by definition identical to the server API, constrained to operations that can be modelled as a direct RPC call to a single server, and carrying server-level routing concerns (e.g. `tenant`) that have no meaning in application code.

This conflation creates several problems:

- **No normative client interface.** There is no specification for what an A2A client library must expose to application code. SDK authors in all six languages independently decide the shape of their client, producing fragmented surfaces with no basis for cross-SDK interoperability testing.

- **Routing is a client concern with no client home.** The `tenant` field must be set correctly on every request, but it is derived from the chosen `AgentInterface` in an `AgentCard` — a resolution step that belongs in the client library, not in application code.

- **No path to network-layer operations.** Operations that are not server RPCs — fan-out to multiple agents, joining shared channels, subscribing to event streams — have no normative place to land. Every attempt to add them (see #1029, #1593, #1995) runs into the fact that the only available hook is the server service definition, which is the wrong place.

- **No tool schema derivable from the client API.** As AI agents increasingly use A2A operations as tools in their reasoning loops, a normative client API becomes the natural source for generating tool definitions. Without one, each SDK and framework defines its own tool surface ad hoc.

## Design Principle: Logical vs Physical Interface

The core idea is to separate the **logical A2A interface** — the set of operations application code and agents program against — from the **physical interface** exposed by individual A2A servers.

The physical interface (`A2AService`) provides the primitives that *enable* the logical interface. An A2A server only needs to implement the operations that make sense for a single agent endpoint. The logical client API may go beyond that: some operations are composed by the client library from multiple physical calls, and others are fulfilled entirely by the transport layer. The server never needs to know.

```
Application / Agent

│ Logical Client API (what you program to)

A2A Client Library

├──────────────────────────────┐
│ │
▼ ▼
A2AService (server) Transport / Broker
Physical primitives Network-layer operations
(per-agent RPC) (fan-out, channels, etc.)
```

A concrete example: `SendMessage` in the logical API accepts one or more `AgentCard` targets. When there is a single target the client library maps it directly to `A2AService.SendMessage` on that server — the physical and logical operations are identical. When there are multiple targets, the client library fans out across them using whatever primitive the transport provides (parallel HTTP calls, multicast, a broadcast). Each individual server still only sees a standard `SendMessage` from `A2AService`. The multi-agent behaviour lives entirely in the logical layer.

This means the physical server spec (`A2AService`) remains stable and minimal — servers implement only what an individual agent endpoint needs to expose — while the logical client API can evolve independently to express richer network-level operations without burdening server authors.

## Describe the solution you'd like

Introduce a normative A2A Client API specification defined in a machine-readable IDL, separate from the existing server API definition. The client API covers exactly the same operations as `A2AService` today, but shaped for application code: each operation carries an `AgentCard` (or repeated `AgentCard`) as its explicit routing target, and all server-level routing details are resolved internally by the transport binding.

Key design points:

- **`SendMessage` is the single send operation**, accepting one or more `AgentCard` targets. A single target is point-to-point; multiple targets trigger transport-level fan-out. There is no separate multi-agent variant.
- **All task operations carry an `AgentCard`** identifying the server that owns the task, replacing the `tenant` field which is an implementation detail resolved from the card.
- **`SendMessageResponse` is always a list of per-agent results**, keeping the return type uniform and allowing partial failures to be reported.

### Logical Client API

```
SendMessage(agents: AgentCard[], message, configuration?) → AgentSendResult[]
SendStreamingMessage(agents: AgentCard[], message, configuration?) → stream StreamResponse

GetTask(agent: AgentCard, id) → Task
ListTasks(agent: AgentCard, filter?) → Task[]
CancelTask(agent: AgentCard, id) → Task
SubscribeToTask(agent: AgentCard, id) → stream StreamResponse

CreateTaskPushNotificationConfig(agent: AgentCard, config) → TaskPushNotificationConfig
GetTaskPushNotificationConfig(agent: AgentCard, task_id, config_id) → TaskPushNotificationConfig
ListTaskPushNotificationConfigs(agent: AgentCard, task_id) → TaskPushNotificationConfig[]
DeleteTaskPushNotificationConfig(agent: AgentCard, task_id, config_id)

GetExtendedAgentCard(agent: AgentCard) → AgentCard
```

### IDL Options

The choice of IDL is an open question for community input. Two options are outlined below.

**Option A: Protocol Buffers** — define the client API as a new `A2AClientService` in a `a2a_client.proto` alongside the existing `a2a.proto`, feeding directly into the existing `buf`-based code generation pipeline. Reuses the existing toolchain with no new dependencies, but proto is primarily designed for server-side RPC service definitions and the client API's multi-target semantics would rely on comments rather than the type system to convey intent.

**Option B: TypeSpec** — [TypeSpec](https://typespec.io) is a language developed by Microsoft for describing APIs and data models, used as the canonical definition language across the Azure SDK. Its `interface` construct models a set of *operations* a client exposes to its callers, which maps more directly to what the client API represents than proto's service model. A single TypeSpec definition can emit OpenAPI, JSON Schema, Protobuf, and client SDK scaffolding from one source. It would introduce a new toolchain dependency and the existing `a2a.proto` data model would need to be bridged.

The full proposal document including code sketches for both options is available at: https://github.com/Tehsmash/A2A/blob/feat/client-api-separation/proposals/client-api-separation.md

### Code Scaffolding

Regardless of IDL choice, the normative definition should drive generation of:

- **Client interface scaffolding** — abstract base classes or interfaces in each SDK language that SDK authors implement.
- **Default transport stub** — a base implementation that returns `UNIMPLEMENTED` for any operation not supported by the transport binding, which richer bindings override.
- **Transport support matrix** — a generated documentation table showing which bindings support which operations.

## Describe alternatives you've considered

**Leave the client API as an SDK convention.** Already happening today; produces fragmented interfaces with no cross-SDK interoperability baseline.

**Extend `A2AService` with client-only operations.** Forces every A2A server to stub out operations that are semantically the transport's responsibility, burdening server authors and weakening conformance testing.

**Per-binding extension definitions only.** No unified client interface to test against; application code must be transport-aware.

## Additional context

**This separation is a prerequisite for several open proposals, not a solution to any single one.** Once a normative client API exists as a separately-versioned surface, the following have a well-defined home without touching `A2AService`:

- **Multi-agent fan-out** — `SendMessage` already accepts multiple `AgentCard` targets; the transport binding handles fan-out. (Relevant: #1029, #1593)
- **Channels and event streams** — operations such as `ListChannels`, `JoinChannel`, and `ListEventStreams` are resolved by the transport or broker layer, not by any individual agent server. The exact naming and semantics are intentionally left open; the point is that the logical client API is the correct place to define them.

**Client operations as agent tools.** A normative client API is the natural source for tool definitions surfaced to AI agents. The same pipeline that generates SDK scaffolding can generate tool definitions for agent frameworks (MCP tool schemas, function calling definitions, etc.) with no per-framework hand-authoring.

**CLI design.** A normative client API provides a direct foundation for the official A2A CLI (#1929). Each client operation maps to a CLI sub-command, with input fields becoming flags and arguments — keeping the CLI in sync with the client spec without additional design work.

## Code of Conduct

- [x] I agree to follow this project's Code of Conduct

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.