ADR-278: A2A is an agent interface in both roles
- Status
-
Accepted
- Date
-
2026-11-10
- Authors
-
Netresearch DTT GmbH
Context
The Agent2Agent protocol (A2A) lets one agent give a task to another over HTTP and follow it to a result. Two requests reached nr-llm: a run should be able to hand a step to a remote agent, and an installation should be able to offer some of its configurations to remote agents. MCP (ADR-116, ADR-259) does not cover either: an MCP server offers tools that the calling model drives step by step, while an A2A agent accepts a task, runs it on its own and reports a state and artifacts.
A2A 1.0 was published as tag v1.0.1 of a2aproject/. The specification
names specification/ as its single normative definition (its
section 1.4); the prose disagrees with the proto in several places, the most
consequential being that Send blocks by default
(Send defaults to false) while the
prose of section 3.1.1 says it returns immediately. A v1.1.0 release candidate
was tagged on 2026-10-29 and adds fields and an error code. Three bindings
exist: JSON-RPC, gRPC and HTTP+JSON/REST. Protocol 0.3 is still in use and is
not compatible with 1.0 (a different blocking flag, a kind discriminator,
other member names).
Decision
nr-llm speaks A2A in both roles. As a client, a run delegates to a remote agent an administrator registered. As a server, nr-llm publishes selected configurations as skills to remote clients an administrator registered. The roles share the protocol code and nothing else; each can be enabled alone.
Protocol 1.0, JSON-RPC binding, nothing else. Requests carry
A2A-. Negotiation compares major and minor only, as the
specification requires: the server accepts 1. and any 1., and the
client accepts a card interface whose protocol_ is 1. or
1.. The server answers an absent version (which the specification
defines as 0.3) and every other major or minor version with
Version (-32009). The REST and gRPC bindings and
protocol 0.3 are not built in either role; they are added when a partner needs
one, by a decision of their own. JSON-RPC is one of the three transports the
conformance kit (a2aproject/) tests, needs no protobuf runtime on
the wire, and runs in a PHP-FPM request.
The normative source is the proto, pinned. a2a. at v1.0.1 (sha256
e195bf96ab630c69797851970203e1b2b6b19528f2e9803b7d904b91a5104016) is
committed as a test fixture. Where prose and proto disagree, nr-llm implements
the proto, and each disagreement is a requirement with its own test
(specs/, "Prose and proto"). Documentation links
point at https://; /latest/
follows the development branch.
nr-llm writes its own JSON-RPC layer and value objects; no A2A SDK is a
runtime dependency. No official PHP SDK exists. The stage-0 spike (report of
2026-11-10, branch spike/) measured praveendias1180/
v1.0.3, generated from the v1.0.0 proto (which differs from v1.0.1 in comments
only):
- Its pure-PHP protobuf runtime (google/protobuf v5.36.2) calls
bccompfor every int32 and timestamp read from JSON. Without() ext-, which TYPO3 14.3 neither requires nor suggests, its core suite fails with 40 errors and 18 failures, andbcmath ListagainstTasks a2a-1.3.0 died with an uncaughtpython Error. The substitutes areext-,bcmath ext-(3 failures remain) or a polyfill (phpseclib plus two packages, 374 files).protobuf - Its client parses responses with unknown fields rejected; a task carrying
v1.1 fields (
generation,status.) failed with "progressHint is unknown". That breaks section 5.7 and the forward compatibility below.progress Hint a2a-1.3.0 read the same responses.python - nr-llm needs its own transport anyway (address guard, explicit 3xx refusal, the vault credential; ADR-279) and its own public types for the client API. In the spike the transport took 95 code lines and a hand-written JSON-RPC client 39, which passed every interop cell the SDK passed plus the missing-version cell the SDK cannot produce.
- It adds four packages, 655 files and 4,680 kB, one of them a Composer plugin; on 2026-11-10 it had 203 downloads, one dependent and one maintainer.
The SDK and a2a- 1.3.0 remain test partners in an interop CI job.
Parsing is forward-compatible. Unrecognised fields are ignored, as the
specification asks (section 5.7). A stream or response member of an unknown
kind is skipped and logged, not treated as an error, so a v1.1 server does not
break the client. An unknown task state is never read as completed, and an
unknown Part kind is skipped and logged rather than read as an empty part.
Optional fields stay optional: a2a- 1.3.0 sends a SUBMITTED status
without timestamp.
Consequences
- ●● One protocol version and one binding keep the surface testable: one proto fixture, one codec, one TCK transport.
- ● Implementing the proto where the prose differs follows the source the
specification itself makes normative; the interop job against
a2a-1.3.0 checks that a reference SDK reads it the same way.python - ◑ A partner that speaks only 0.3, REST or gRPC cannot connect until a further decision adds it.
- ◑ A v1.1 server can send things the client skips; nothing of v1.1 is used.
- ✕ Two new attack surfaces exist where there were none; ADR-280 and the specification's security requirements bound them.
Net Score: +3
Alternatives considered
Client role only. Rejected: the server role was requested, and building the protocol code once for both roles costs less than twice.
Support 0.3 as well. Rejected for now: two incompatible wire formats double the codec and the tests for no current partner.
Use praveendias1180/ as codec or client. Rejected for the
measured reasons in the decision: a bcmath requirement TYPO3 does not carry,
strict parsing against section 5.7, and nothing left for it to do once the
transport and the public types are nr-llm's own.
Follow the prose where it is explicit. Rejected: the specification itself
makes the proto normative, and a non-blocking Send would break every
1.0 client that relies on the default.