a2aproject / a2aproject/A2A

[Proposal] New experimental repository: experimental-cpb-websocket

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

Description

# WebSocket Custom Protocol Binding for A2A - Proposal

## Abstract

This proposal requests the creation of an `experimental-cpb-websocket` repository under the `a2aproject` GitHub organization to host the **WebSocket** custom protocol binding for A2A.

The binding carries the **existing** A2A method inventory - the same operations, request/response shapes, and server-streaming semantics defined by the core specification and used by the JSON-RPC binding - over a single, long-lived [WebSocket](https://datatracker.ietf.org/doc/html/rfc6455) connection. It is layered:

- **Transport:** WebSocket (RFC 6455) provides nothing but an ordered, message-framed, full-duplex byte pipe over an HTTP/1.1 `Upgrade` handshake.
- **Application:** [JSON-RPC 2.0](https://www.jsonrpc.org/specification) - the **same** request, response, and numeric-coded error objects as the A2A JSON-RPC binding - carried one object per WebSocket text frame.

On top of these, the binding adds a small set of **A2A extension frames** (multiplexing by request `id`, a `streamEnd` terminator, and a `cancelStream` control frame) that exist only because one persistent connection now carries many concurrent RPCs.

The binding is named simply **WebSocket** after its transport layer. Multiplexing, streaming termination, and in-band control are a thin A2A layer added on top of standard WebSocket-as-transport, with JSON-RPC 2.0 as the unchanged message format on the wire.

The binding **does not introduce any new operation or streaming semantic to A2A**. It uses the WebSocket connection purely as a *multiplexed RPC transport*: a single connection carries many concurrent request/reply and server-stream RPCs, distinguished by `id`. The connection's full-duplex nature is used only so that a client can issue and cancel independent RPCs at any time - including while server-streaming responses for other requests are still in flight - not to define a new bidirectional-streaming operation.

## Motivation

The core A2A specification defines three standard bindings: HTTP+JSON, HTTP JSON-RPC, and gRPC. The two HTTP/1.1 bindings carry one RPC per HTTP request, and server-streaming RPCs (e.g. `SendStreamingMessage`) are delivered over Server-Sent Events - one long-lived HTTP response per stream. gRPC multiplexes many RPCs and server-streams over one HTTP/2 connection, but requires Protocol Buffer tooling and HTTP/2 end-to-end, and is not natively available in browsers.

The WebSocket binding occupies a distinct point in this space: it carries the **same** A2A RPCs - same methods, same JSON-RPC 2.0 objects, same server-streaming semantics - but multiplexes many of them over a single HTTP/1.1-originated connection. Its value is in the *transport*, not in any new protocol semantic:

- **App-controlled RPC multiplexing over a single HTTP/1.1 connection:** A client can have many request/reply RPCs and server-streams in flight concurrently over one connection it explicitly owns, correlated by request `id`. With the JSON-RPC binding this requires either one HTTP request per RPC or one SSE response per stream; gRPC offers multiplexing but only over HTTP/2 with protobuf tooling, and that multiplexing is managed by the runtime rather than the application.

- **In-band client -> server control on a live stream:** While a server-streaming RPC is running, the client can send other framed messages on the same connection - for example a `cancelStream` control frame to stop a specific stream, or a new `SendMessage` RPC. SSE has no client->server channel at all (cancellation means tearing down the whole HTTP response), so this is something the HTTP/1.1 bindings cannot express. Note this is still ordinary A2A: each client turn is its own RPC; the binding does not define a bidirectional-streaming operation.

- **Browser-native, no extra tooling:** WebSocket is available in every modern browser via the `WebSocket` API, with no protobuf compiler, `grpc-web` proxy, or polyfill. This suits browser-based agent UIs and lightweight edge clients where the gRPC binding is impractical and per-stream SSE connections are awkward to manage.

- **Connection-scoped authentication and state:** Credentials are presented once at the upgrade handshake and apply to every RPC on the connection, rather than being attached to each request. The server can also associate per-connection state (active subscriptions, in-flight streams) with the connection lifetime.

- **Firewall and proxy traversal:** The connection is established via a standard HTTP/1.1 `Upgrade` handshake, which passes through most corporate firewalls, load balancers, and reverse proxies that may not support HTTP/2 (needed for gRPC) or that buffer/terminate long-lived SSE responses.

None of these are new A2A capabilities - they are transport characteristics that the existing HTTP/1.1 bindings cannot offer together, which is what warrants a dedicated custom protocol binding rather than a change to the core protocol.

### Why WebSocket rather than HTTP/2 multiplexing?

A reasonable objection is that HTTP/2 already multiplexes many HTTP requests over a single connection via its connection pool, so this binding might appear redundant. It is not, and the reasons are worth stating precisely because the common guesses ("HTTP/2 isn't in the browser" / "HTTP/2 can't stream") are both wrong:

- **HTTP/2 *is* available in browsers, and it *does* stream.** Browsers speak HTTP/2 for ordinary requests, and A2A's server-streaming RPCs already work over HTTP/2 (the gRPC binding depends on exactly that) and over HTTP/1.1 via SSE. So neither browser availability nor streaming capability is the differentiator.

- **HTTP/2 multiplexing is runtime-managed, not application-managed.** With `fetch`/`XHR`, the browser decides how requests map onto connections and streams. Application code cannot pin all its A2A RPCs to one specific connection, cannot observe stream boundaries, and gets no application-level framing or control channel. WebSocket gives the application one explicit connection it owns and frames itself - which is what makes connection-scoped auth and per-connection state well-defined.

- **The real gap is the client->server direction on a live stream.** With SSE and plain HTTP there is no application channel to send a message *to* the server while a response stream is open. WebSocket's full-duplex frames let the client send `cancelStream`, a credential refresh, or a brand-new RPC on the same connection while events are still arriving.

- **HTTP/2 is not guaranteed end-to-end.** Intervening proxies and middleboxes can silently downgrade HTTP/2 to HTTP/1.1, collapsing the multiplexing the application relied on. A WebSocket upgrade is a single explicit HTTP/1.1 handshake that those intermediaries generally pass through.

- **gRPC (the HTTP/2 binding) needs tooling browsers don't have.** It requires a `grpc-web` proxy and protobuf compilation. WebSocket needs none of that.

In one sentence: the advantage over HTTP/2 is **application-controlled, browser-native, full-duplex framing over one explicitly owned connection - with in-band client->server signaling on live streams and no protobuf/grpc-web tooling** - which no existing binding offers together.

## Specification

A draft specification for the WebSocket binding is provided [here](https://github.com/hackeramitkumar/A2A/blob/websocket_spec/docs/bindings/websocket.md). The binding uses JSON-RPC 2.0 over WebSocket text frames, with the same data model, method inventory, and numeric error codes as the JSON-RPC binding. Request/reply RPCs return a single JSON-RPC response; server-streaming RPCs (`SendStreamingMessage`, `SubscribeToTask`) return a sequence of JSON-RPC `result` frames sharing the request `id`, terminated by a `streamEnd` extension frame - the same server->client streaming model and payloads as the other bindings, delivered over WebSocket frames instead of SSE. The specification covers connection lifecycle, the JSON-RPC message framing, multiplexing of concurrent RPCs by request `id`, error handling, authentication, and reconnection behavior.

## Reference Implementations

Reference implementations are planned for:

- **Rust:** As part of the `a2a-rs` SDK (in-progress)
- **Python:** Planned
- **TypeScript/JavaScript:** Planned (browser-compatible client)

## Request

Per the Extension and Protocol Binding Governance process, this proposal requests:

1. An A2A Maintainer to sponsor this proposal
2. Creation of the `experimental-cpb-websocket` repository under `a2aproject`
3. The initial content of the repository would be the specification draft and links to reference implementations

## References

- [WebSocket Protocol (RFC 6455)](https://datatracker.ietf.org/doc/html/rfc6455)
- [WebSocket API (W3C)](https://websockets.spec.whatwg.org/)
- [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification)
- [A2A Protocol Specification v1.0.0](https://a2a-protocol.org/v1.0.0/specification/)
- [A2A JSON-RPC Protocol Binding](https://a2a-protocol.org/v1.0.0/specification/#9-json-rpc-protocol-binding)
- [A2A Custom Protocol Bindings](https://a2a-protocol.org/latest/topics/custom-protocol-bindings/)
- [A2A Extension and Protocol Binding Governance](https://a2a-protocol.org/latest/topics/extension-and-binding-governance/)

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.