praveendias1180 / a2a-php
PHP SDK for the A2A (Agent2Agent) protocol: client, server and types, in the same shape as the official Python SDK.
Requires
- php: ^8.2
- google/common-protos: ^4.12
- google/protobuf: ^4.33 || ^5.0
- php-http/discovery: ^1.19
- psr/clock: ^1.0
- psr/event-dispatcher: ^1.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^1.1 || ^2.0
- psr/http-server-handler: ^1.0
- psr/log: ^2.0 || ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- guzzlehttp/guzzle: ^7.9
- illuminate/support: ^11.0 || ^12.0 || ^13.0
- nyholm/psr7: ^1.8.2
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
- symfony/http-client: ^6.4 || ^7.0 || ^8.0
Suggests
- ext-pdo: PdoTaskStore and PdoQueueManager (SQLite, PostgreSQL or MySQL), needed to serve agents under PHP-FPM or php -S
- ext-protobuf: Faster protobuf encoding and decoding than the pure-PHP runtime
- nyholm/psr7: A PSR-7 implementation for the server (any PSR-7/PSR-17 library found by php-http/discovery works)
- praveendias1180/a2a-laravel: Laravel integration: routes, queued task runner, Redis streaming, Eloquent stores
Provides
None
Conflicts
None
Replaces
None
README
A PHP implementation of the A2A (Agent2Agent) protocol, built in the same shape as the official Python SDK. If you know the Python SDK, you already know this one: the same classes in the same places, with camelCase methods.
Targets A2A 1.0, the current spec. Types are generated from the official a2a.proto, and the server passes the official A2A test kit (TCK) at the MUST, SHOULD and MAY levels over JSON-RPC and HTTP+JSON, checked in CI on every push, both as plain PHP and as a Laravel app with queued execution. Works with any framework (PSR-7/15/17/18), with a Laravel bridge.
📖 Documentation: https://praveendias1180.github.io/a2a-php/
Status: early development (phase 5 of 7). The wire types, utilities, client, server and Laravel bridge are done. The server passes the official A2A TCK (plain PHP, and a Laravel app running executors on queue workers), and the SDK interoperates with the official Python SDK in both directions. Push notifications and card signing are next. The API may still change before 1.0. See the roadmap.
Packages
| Package | What it is |
|---|---|
praveendias1180/a2a-php |
The SDK itself. Works with any framework, built on PSR-7/15/17/18. This repo. |
praveendias1180/a2a-laravel |
Laravel bridge: Route::a2a(), queued execution on your workers with live SSE streaming (Redis Streams or the database), owner-scoped storage, artisan commands. Lives in packages/laravel and is published as a read-only split. |
Requires PHP 8.2+.
composer require praveendias1180/a2a-php
Protocol support
| A2A spec | Status |
|---|---|
| 1.0 | supported since v0.1.0. The server passes the official A2A TCK at the MUST, SHOULD and MAY levels over JSON-RPC and HTTP+JSON; the client interoperates with the official Python SDK in both directions. |
| 0.3 | planned as a compatibility layer (phase 6) |
Types are generated from the official a2a.proto (v1.0.0, the same pin as the Python SDK). JSON on the wire is standard ProtoJSON.
Call an agent (works today)
use A2A\Client\ClientFactory; use A2A\Helpers\ProtoHelpers; use A2A\Types\{Role, SendMessageRequest}; $client = ClientFactory::createClient('https://agent.example.com'); // reads the Agent Card $request = new SendMessageRequest(['message' => ProtoHelpers::newTextMessage('hello', role: Role::ROLE_USER)]); foreach ($client->sendMessage($request) as $event) { // streams (SSE) when the agent supports it if ($event->hasArtifactUpdate()) { echo ProtoHelpers::getArtifactText($event->getArtifactUpdate()->getArtifact()), PHP_EOL; } }
JSON-RPC and HTTP+JSON, every A2A operation, tested in CI against the official Python SDK's sample agent. Works with Guzzle or Symfony HttpClient (live streaming) or any PSR-18 client. More in Call an agent.
Serve an agent
The SDK's examples/hello-world (a port of the Python SDK's hello_world_agent.py, run in CI against the official Python client and the A2A TCK):
final class HelloExecutor implements AgentExecutor { public function execute(RequestContext $context, EventQueue $eventQueue): void { $userMessage = $context->message(); $taskId = $context->taskId(); $contextId = $context->contextId(); if ($userMessage === null || $taskId === null || $contextId === null) { return; } $eventQueue->enqueueEvent(new Task([ 'id' => $taskId, 'context_id' => $contextId, 'status' => new TaskStatus(['state' => TaskState::TASK_STATE_SUBMITTED]), 'history' => [$userMessage], ])); $updater = new TaskUpdater($eventQueue, $taskId, $contextId); $updater->startWork($updater->newAgentMessage([new Part(['text' => 'Processing your question...'])])); $reply = $this->parseInput($context->getUserInput()); sleep(1); // Python tracks running tasks in a set; a PHP request can be // cancelled from another process, so ask the context instead. if ($context->isCancelled()) { return; } $updater->addArtifact([new Part(['text' => $reply])], name: 'response', lastChunk: true); $updater->complete(); } // cancel() and parseInput(): see examples/hello-world/HelloExecutor.php }
Serving it (examples/hello-world/server.php, with the card defined above that point):
$pdo = new PDO('sqlite:' . (getenv('A2A_DB') ?: sys_get_temp_dir() . '/a2a-php-hello-world.sqlite')); $handler = new DefaultRequestHandler( agentExecutor: new HelloExecutor(), taskStore: new PdoTaskStore($pdo), agentCard: $agentCard, queueManager: new PdoQueueManager($pdo), ); $router = Routes::router($handler, $agentCard, jsonRpcPath: '/a2a/jsonrpc', restPrefix: '/a2a/rest'); (new ResponseEmitter($handler))->emit($router->handle($request));
PHP_CLI_SERVER_WORKERS=4 php -S 127.0.0.1:41241 examples/hello-world/server.php
Every PHP request is its own process, so tasks and events live in a shared database (SQLite here). More in Your first agent.
In Laravel
composer require praveendias1180/a2a-laravel
php artisan vendor:publish --tag=a2a-migrations && php artisan migrate
php artisan a2a:make-executor Hello
// routes/api.php (from examples/laravel) Route::a2a('/a2a', agentCard: HelloAgentCard::class, executor: HelloExecutor::class);
That mounts the Agent Card, JSON-RPC and HTTP+JSON. Set A2A_RUNNER=queued and the executor runs on your queue workers while the web request streams its events live. The Laravel guide has the rest.
Roadmap
| # | Phase | Done when |
|---|---|---|
| 0 | Skeleton, CI, generated types | ✅ |
| 1 | Types + utilities (errors, helpers, validators) | ✅ |
| 2 | Client (JSON-RPC + REST + SSE) | ✅ |
| 3 | Server core | ✅ |
| 4 | Laravel bridge | ✅ |
| 5 | Push notifications, card signing, extensions (the PDO stores arrived early, in phase 3) | the TCK passes at the SHOULD level |
| 6 | v0.3 compatibility | a 0.3 client works against a 1.0 server |
| 7 | 1.0.0 | stable release |
Design notes: Architecture. The class-by-class mapping to the Python SDK: Python → PHP mapping.
Development
composer install composer test # PHPUnit composer analyse # PHPStan (level max) composer cs # code style check composer generate # regenerate generated/ from a2a.proto (needs Node for npx)
Docs site (in docs/, built with Zensical):
pip install zensical
zensical serve # http://127.0.0.1:8000/a2a-php/
See CONTRIBUTING.md.
License
Apache-2.0. See LICENSE and NOTICE. This project is not affiliated with the A2A Project or the Linux Foundation.