Featured work
a2a-php-sdk
Client and server SDK for the A2A 1.0.0 protocol in PHP 8.4 — all 11 operations over JSON-RPC, REST and gRPC, with SSE and gRPC streaming, run against the official A2A TCK.
- PHP 8.4
- A2A 1.0.0
- JSON-RPC
- HTTP+JSON / REST
- gRPC (OpenSwoole)
- SSE
- Protocol Buffers
- Symfony HttpClient
- PHPStan level 8
- Docker
The problem
A2A (Agent2Agent) is the open protocol, now a Linux Foundation project, that lets agents built on different frameworks call each other. Version 1.0.0 was released on 12 March 2026. The official SDKs cover Python, JavaScript, Java, Go, .NET and Rust — not PHP. The PHP implementations on Packagist, as of September 2026, target protocol 0.3.0.
That leaves Symfony and plain PHP backends unable to expose an agent to the A2A ecosystem, or call one from it, without hand-writing the wire format.
What it is
- Client and server in one package, PHP 8.4+, no Laravel/Illuminate dependency.
- Message classes are generated from the official v1.0.0
a2a.proto. CI regenerates them and compares byte for byte, and the proto’s SHA-256 is recorded in the repository. - All 11 operations —
sendMessage,sendStreamingMessage,getTask,listTasks,cancelTask,subscribeToTask, four push-notification config operations andgetExtendedAgentCard— over JSON-RPC, HTTP+JSON/REST and gRPC, through the same typed client methods. - SSE and gRPC server streaming share one task service and one validation path.
$client = new Client(
new HttpTransport(HttpClient::create(), $endpoint, jsonRpc: true),
new CallOptions(timeoutSeconds: 30, headers: ['Authorization' => 'Bearer '.$token]),
);
$response = $client->sendMessage($request);
foreach ($client->sendStreamingMessage($request) as $event) {
$consumer->accept($event);
}
Server side
An agent is one interface, Executor, which yields status and artifact updates for a task. The SDK creates the task, records the user message, emits WORKING, keeps history and streaming events, and delivers webhooks.
- File storage uses locks and atomic replacement. Records carry caller identity and tenant, so tasks are scoped to whoever created them.
- Long-running work goes through
returnImmediatelyand workers that callProcessor::workOnce(). - Webhooks are delivered at least once, require HTTPS and an explicitly allowed host; private-network destinations are blocked by default.
- The native gRPC server runs on OpenSwoole 26.2+ and stays off unless enabled.
Safe defaults
- Agent Card discovery bounds response sizes and blocks private-network targets. No endpoint is picked automatically from an untrusted card.
- Client calls are not retried automatically: retrying
sendMessage()can repeat work, so the retry policy belongs to the application. - TLS is on by default for the gRPC client. The test suite covers TLS and mTLS on all three bindings, including an untrusted CA, a wrong server name and missing client certificates.
Conformance, stated precisely
make tck builds the official a2a-tck 1.0.0 from pinned sources and runs it against all three transports. The unchanged suite reports 244 passed, 3 failed, 18 skipped.
The three failures are one requirement, CORE-SEND-003, on each transport. It says unsupported media MUST return ContentTypeNotSupportedError, but the TCK’s requirement definition omits the expected error, so the correct response is scored as a failure. The SDK returns the required error: -32005 for JSON-RPC, HTTP 415 for REST, INVALID_ARGUMENT with CONTENT_TYPE_NOT_SUPPORTED for gRPC. The defect is tracked upstream in a2a-tck#202; with the fix from PR #203 the run is 247 passed, 18 skipped.
CI accepts exactly those three documented failures and fails on anything new. This is not a claim of full TCK certification.
Shipping
- CI on every pull request, push and tag: locked install and vulnerability audit, unit and network tests, PHPStan level 8, PSR-12, the protocol regeneration check, and a ZIP installed into a fresh consumer project without gRPC extensions.
- Released as v1.0.0 on Packagist under Apache-2.0:
composer require vbcherepanov/a2a-php-sdk. - Listed in the official A2A community SDKs (merged 7 October 2026).
- Symfony applications get routes, workers, Doctrine storage and Messenger through the companion a2a-symfony-bundle.
Lessons
- A conformance suite is software too. Reading why a requirement failed located the defect in the test, not in the SDK — and the right response was to report it upstream rather than bend the SDK to a wrong assertion.
- Generating types from the official proto, and checking the regeneration in CI, removes a whole class of drift between the specification and hand-written classes.