Skip to content

Changelog

All notable changes to this project are documented here. The format follows Keep a Changelog and the project uses Semantic Versioning.

Unreleased

1.0.3 - 2026-10-14

Fixed

  • The PDO stores work on a SQLite connection that is already in a transaction. Building PdoTaskStore, PdoQueueManager or PdoPushNotificationConfigStore set journal_mode and synchronous, which SQLite refuses inside a transaction ("Safety level may not be changed inside a transaction"). So every A2A request failed with a 500 in a Laravel app tested with RefreshDatabase on SQLite (the default for new Laravel apps), and in any code that builds a store inside DB::transaction(). Those two settings are now skipped while a transaction is open; busy_timeout is still set. Found while building the Laravel starter app.

1.0.2 - 2026-10-12

Catch-up with the Python SDK from 0d5473c to 494a8ec (a2a-sdk 1.2.0 to 1.2.2). Interop tests now pin a2a-sdk 1.2.2.

Fixed

  • ListTasks page tokens survive the listing changing. A token named the first task of the next page and was looked up again, so deleting that task made the next request fail with InvalidParamsError, and updating it restarted the listing with duplicates. Tokens now carry the sort position of the last task returned (TaskUtils::encodeListTasksCursor()), and the next page starts strictly after it. Tokens issued by 1.0.x are still accepted. Both InMemoryTaskStore and PdoTaskStore; Python b7cba7c.
  • CreateTaskPushNotificationConfig returns the id the config was stored under. A config created without an id was stored under the task id but returned with an empty id, so the client could not get or delete it. The new PushNotificationConfigs::normalize() is what every store saves and what the handler returns; the store interface is unchanged. Python dac7d9d.
  • JCS depth bound counts open containers, the outermost at 1, so an empty object or array at depth 129 is refused like any other container past the bound (it was accepted). Also in Signing::cleanEmpty(). Python 5653daf.
  • The client prefers an A2A 1.0 interface regardless of its patch version (1.0.0 and 1.0.3 count as 1.0). Before, 1.0.0 was treated as "above 1.0" and could lose to another interface. Python cbb2d84.
  • A wrongly shaped response no longer slips through as a PHP warning. The pure-PHP protobuf runtime warns and carries on for some shape errors (for example securitySchemes as a string); the client now raises A2AClientError for those, as for any unreadable response. Python 486f0f6.
  • Agent Card If-None-Match uses the weak comparison and accepts * (RFC 9110). Proxies that weaken ETags, such as nginx when it gzips, got a full response every time instead of a 304. Python 83d7f5d.

Changed

  • A JSON-RPC request whose params cannot be parsed gets InvalidParamsError with data.parseError holding the parser's message. Python 83f1cf8.

Added

  • Dependabot: weekly grouped update PRs for GitHub Actions, Composer (widen, so supported ranges are kept) and the interop test packages. Dependabot security alerts and security updates are enabled.
  • ProtoUtils::warnOnMissingRequiredFields(): DefaultRequestHandler and the Agent Card route (given a logger) log a warning when a card misses REQUIRED fields, without failing. Python 4f4101c.
  • Tests: the cross-SDK Agent Card signature vectors (cards signed by a2a-python, @a2a-js/sdk, a2a-go and a reference signer verify in PHP, with identical canonical bytes) and the jcs_depth_v1 depth vectors, both copied from a2a-python.

Not ported yet

  • Python's cluster mode (a2a.server.cluster, versioned task store). PHP already shares task state across processes through QueueManager; what is missing is versioned task writes. See the Python → PHP mapping.
  • Python's opt-in input-mode validation.
  • Python's application/a2a+json responses (dc5a5da). Spec v1.0.0 and the A2A TCK still require application/json, so HTTP+JSON keeps it.

1.0.1 - 2026-10-12

Documentation, plus one static-analysis fix with no behaviour change.

Fixed

  • a2a-laravel: RunAgentExecutor no longer passes both JSON_THROW_ON_ERROR and JSON_PARTIAL_OUTPUT_ON_ERROR to json_encode when recording a failed run. PHP already let partial output win, so the result is identical; PHPStan 2.3 now rejects the combination.

Added

  • Docs: "Check the agent first with the A2A CLI" in Call an agent, showing how to test an agent with the official A2A CLI before debugging PHP code, including its --echo and --proxy server modes. Suggested by an A2A maintainer when the SDK was listed on the Community SDKs page.
  • README: a short tip pointing to it.

1.0.0 - 2026-09-26

The stable release. No API changes since 0.3.0 beyond marking implementation details @internal; from here the public API follows Semantic Versioning. Both packages are released as 1.0.0; a2a-laravel requires a2a-php ^1.0. Upgrading from 0.x: see the upgrade guide.

Added

  • Backward-compatibility policy (docs/reference/backward-compatibility.md): what 1.x covers (public classes, interfaces you implement, exceptions, Laravel config and routes, PDO schema, wire behaviour, supported PHP/Laravel versions) and what it doesn't (@internal, generated-code layout, the spec itself).
  • API reference generated with phpDocumentor (phpdoc.dist.xml) and published under /api/ on the docs site. @internal classes are left out.
  • Upgrade guide (docs/guides/upgrading.md): 0.1 → 0.2 → 0.3 → 1.0, and the policy from here.
  • Benchmarks: scripts/benchmark.php (ProtoJSON, SSE parsing, full JSON-RPC requests) with results on docs/reference/performance.md, and a manual benchmark.yml workflow that compares the pure-PHP and ext-protobuf runtimes.
  • Upstream watch: scripts/upstream-watch.py and a weekly upstream-watch.yml workflow that keep one GitHub issue listing new upstream commits (Python SDK, a2a.proto, TCK requirements) since the pins in UPSTREAM.md.
  • Docs sample test (tests/Docs/DocsCodeSamplesTest.php): every inline PHP sample in docs/ must parse, and every A2A class and static method it names must exist.

Changed

  • Marked @internal (not covered by the 1.x promise, left out of the API reference): Client\Http\HttpSenderFactory, Client\Sse\EventStreamParser, Client\Sse\SseEvent, Server\AgentExecution\ActiveTaskRegistry, Server\AgentExecution\EventConsumer, Server\RequestHandlers\ResponseHelpers, Server\Routes\Common, Utils\Jcs, every class in Compat\V0_3 (conversions, adapters, 0.3 handlers and transports), and in the Laravel bridge Routing\A2ARoutes, Auth\LaravelUser, Auth\SerializedUser and the four console command classes. These were already implementation details (plus the 11 classes that were already @internal); the public route to each is in the upgrade guide.
  • The docs site is built from the newest v* release tag instead of main, so it never documents unreleased features (the workflow still runs from main; a tag push re-runs it).
  • praveendias1180/a2a-laravel requires praveendias1180/a2a-php ^1.0.
  • README and docs describe the 1.x status; badges for CI, the TCK result, Packagist version, PHP version and license.
  • docs/guides/a2a-0-3.md: the 0.3 server example is now a complete, valid snippet.

0.3.0 - 2026-09-26

Phases 5 and 6: push notifications, Agent Card signing and extensions, and A2A 0.3 compatibility (server and client). The TCK MUST level now passes 161 distinct tests (was 137), in plain PHP and on a queued Laravel app. Both packages are released as 0.3.0; a2a-laravel requires a2a-php ^0.3.

Added

  • A2A 0.3 compatibility (A2A\Compat\V0_3, a port of Python's a2a.compat.v0_3).
  • Client: ClientFactory uses CompatJsonRpcTransport / CompatRestTransport for a v0.3 interface, so a 0.3 agent is called with the usual v1.0 types. Cards that only offer 0.3 no longer raise.
  • Server: JsonRpcDispatcher, RestDispatcher, Routes::jsonRpc(), Routes::rest() and Routes::router() take enableV03Compat (off by default, as in Python). With it on, the v0.3 JSON-RPC methods and the v0.3 REST routes (/v1/...) are served on the same endpoints through JsonRpc03Adapter / Rest03Adapter.
  • Agent Card: a card that offers a v0.3 interface is served with the v0.3 fields merged in (ResponseHelpers::agentCardToDict()).
  • Building blocks: Conversions (every v0.3 ↔ v1.0 conversion), ToProto / FromProto, the v0.3 proto types (A2A\Compat\V0_3\Types, generated from the v0.3 proto Python pins, vendored as proto/v0_3/a2a_v0_3.proto), RequestHandler03, Versions, ExtensionHeaders, V03ServerCallContextBuilder (accepts X-A2A-Extensions).
  • Laravel: a2a.v0_3_compat / A2A_V0_3_COMPAT. When the card's interfaces are filled in from the routes, the v0.3 ones are added too.
  • Interop: tested against a2a-sdk 0.3.26 in both directions (scripts/run-python-interop-v03.sh, scripts/run-tck-laravel.sh v03-interop, both in CI). Docs: "Talking to A2A 0.3 agents and clients".
  • Push notifications. PushNotificationSender + BasePushNotificationSender: every task update is POSTed to the task's webhooks as a StreamResponse, with Authorization (from the config's authentication), X-A2A-Notification-Token and a stable X-A2A-Notification-Id; retries network errors, 408, 425, 429 and 5xx with exponential backoff (honours Retry-After); never follows redirects. Wired into DefaultRequestHandler (pushSender:), sent after each update is saved, in order; a failing sender never fails the task.
  • SSRF protection for webhooks: URLs are checked when a config is created and again before every attempt; with Guzzle (ext-curl) or Symfony HttpClient the connection is pinned to the checked address (DNS-rebinding safe). PushUrlValidator::resolve() and allowedHosts (for a local receiver in development). HttpRequest gains pinnedAddress and followRedirects; PinsAddresses marks senders that can pin.
  • PdoPushNotificationConfigStore (SQLite, PostgreSQL, MySQL) with optional encrypt/decrypt closures; PushNotificationConfigStore::getInfoForDispatch() (every owner's configs for a task).
  • Agent Card signing (Utils\Signing, needs firebase/php-jwt): createAgentCardSigner(), createSignatureVerifier(), canonicalizeAgentCard(), cleanEmpty() and the SignatureVerificationError family, ported from Python's utils/signing.py. Routes::agentCard(..., signer:) / Routes::router(..., cardSigner:) serve the card signed (a copy, signed once per process). Cross-SDK check in CI: Python-signed cards verify in PHP and PHP-signed cards verify in Python (ES256, HS256).
  • Extensions. Required extensions are enforced (ExtensionSupportRequiredError before the executor runs), requested extensions the card declares are activated, and activated extensions are echoed in the A2A-Extensions response header on JSON-RPC and HTTP+JSON. ServerCallContext::$activatedExtensions, RequestContext::activatedExtensions() / isExtensionActive() / activateExtension(), Extensions\Common::activatableExtensions() / missingRequiredExtensions(). Example extension: examples/extensions/TimestampExtension.php.
  • Laravel: push notifications sent from a queue job (SendPushNotification; credentials never enter the queue payload), a2a.push config (queue, attempts, backoff, timeout, allowed hosts), webhook URLs checked on create; Agent Card signing via a2a.signing (public and extended card).
  • TCK: tck/sut-agent.php and the Laravel TCK app take A2A_SUT_PROFILE (minimal, full, required-extension); scripts/run-tck.sh and scripts/run-tck-laravel.sh run every profile, so the push-notification, extended-card and required-extension requirements now run instead of being skipped.
  • Docs: guides for push notifications, card signing and extensions.

Security

  • The card-signature verifier never uses PEM or OpenSSH public-key material as an HMAC secret, so a card forged with HS256 and the public key is rejected even when the allow-list mixes HMAC and public-key algorithms (the same guard PyJWT has).

Changed

  • PushNotificationConfigStore gained getInfoForDispatch(): custom stores must implement it.
  • DefaultRequestHandler's pushUrlValidator also accepts a PushUrlValidator instance.
  • The Laravel bridge now checks webhook URLs when a push config is created (private and unresolvable hosts are rejected).

0.2.0 - 2026-09-25

Adds the Laravel bridge, released as praveendias1180/a2a-laravel 0.2.0 (both packages are versioned together from here on). The official A2A TCK passes at the MUST level against a queued Laravel app on PHP-FPM + nginx.

Added

  • Laravel bridge (praveendias1180/a2a-laravel, packages/laravel), for Laravel 12 and 13 (11 allowed, untested):
  • Route::a2a('/a2a', agentCard: ..., executor: ...) mounts the Agent Card, JSON-RPC and HTTP+JSON, fills the card's interfaces in from the routes, and is route:cache safe. Agents can also come from config/a2a.php.
  • QueuedTaskRunner + RunAgentExecutor job: executors run on queue workers while the web request streams their events live; one run per task at a time (run lease); executor errors fail the call the same way as with the inline runner.
  • RedisQueueManager (Redis Streams, XREAD BLOCK, atomic sequence numbers, expiring keys), or the core PdoQueueManager on the app's connection.
  • Tasks in the core PdoTaskStore on the app's connection; push-notification configs in the database, encrypted with the app key. Owner scoping from the authenticated Laravel user.
  • Security schemes in the card map to route middleware (a2a.security_schemes); CSRF is off for the protocol routes.
  • A2A::client() facade; artisan a2a:make-executor, a2a:card, a2a:prune, a2a:tck; publishable config and migration.
  • scripts/run-tck-laravel.sh: the official A2A TCK against a real Laravel app on PHP-FPM + nginx with the queued runner and queue workers (also a CI job), and a 20 s long-task proof.
  • ResponseEmitter::streamSse(): the SSE pump, public so framework bridges stream exactly like the plain-PHP emitter.
  • examples/laravel/: the Laravel examples shown in the docs, run by the test suite.
  • Docs: Laravel guides (install and first agent, queued execution, streaming behind nginx, storage and pruning).

Changed

  • praveendias1180/a2a-laravel requires praveendias1180/a2a-php ^0.2.
  • tck/TckAgentExecutor.php: the TCK executor moved out of tck/sut-agent.php, so the Laravel TCK app reuses it.

0.1.0 - 2026-09-25

First release: A2A 1.0 client and server for PHP 8.2+. The server passes the official A2A TCK at the MUST level (137 passed) over JSON-RPC and HTTP+JSON. The Laravel bridge is not included yet (phase 4).

Added

  • Wire types generated from the A2A v1.0.0 a2a.proto (A2A\Types\*).
  • A2A\Utils\Constants and A2A\Utils\TransportProtocol.
  • ProtoJSON round-trip tests against the specification's JSON shapes.
  • Laravel bridge package skeleton (packages/laravel).
  • Errors (A2A\Utils\Errors\*): one exception per A2A and JSON-RPC error, with the same JSON-RPC codes, HTTP statuses, gRPC statuses and ErrorInfo reasons as the Python SDK (ErrorMapping).
  • A2A\Utils\ErrorHandlers: REST error payloads and JSON-RPC error objects, including google.rpc.ErrorInfo and google.rpc.BadRequest details. Unlike the Python SDK, a non-A2A exception becomes JSON-RPC -32603 "Internal error" without its message, so internal details never reach the caller (REST already hid them).
  • A2A\Utils\ProtoUtils: stream-response wrapping, Struct/Value ↔ PHP conversion, REST query-parameter parsing, and REQUIRED-field validation driven by a table generated from a2a.proto (A2A\Types\Meta\RequiredFields, scripts/generate-required-fields.php).
  • A2A\Utils\TaskUtils (history length, page size, page tokens), JsonUtils, VersionValidator (A2A-Version header) and PushUrlValidator (SSRF guard for push-notification URLs, with an injectable resolver).
  • A2A\Utils\Jcs: RFC 8785 JSON canonicalization, checked against the a2a-jcs-v01 vector corpus and against ECMAScript number formatting.
  • A2A\Helpers\ProtoHelpers and AgentCardHelpers, A2A\Extensions\Common, A2A\Auth\User and UnauthenticatedUser.
  • The client (A2A\Client\*), in the shape of the Python SDK's a2a.client: ClientFactory (with createClient() and minimalAgentCard()), BaseClient, ClientConfig, ClientCallContext, A2ACardResolver (including the pre-1.0 card field mapping), interceptors (ClientCallInterceptor, BeforeArgs, AfterArgs), AuthInterceptor with InMemoryContextCredentialStore, service parameters, and the client errors (A2AClientError, A2AClientTimeoutError, AgentCardResolutionError).
  • JSON-RPC and HTTP+JSON transports (JsonRpcTransport, RestTransport, TenantTransportDecorator) with every A2A operation. Errors from the agent come back as the matching A2A\Utils\Errors\* exception. Streams are PHP Generators.
  • A2A\Client\Http\HttpSender with senders for Guzzle and Symfony HttpClient (live SSE streaming) and any PSR-18 client (buffered), picked by HttpSenderFactory; an incremental SSE parser (A2A\Client\Sse\EventStreamParser).
  • Interop tests against the official Python SDK's sample agent (a2a-sdk 1.1.5): every client operation over JSON-RPC and HTTP+JSON, with Guzzle, Symfony HttpClient and a PSR-18 client (scripts/run-python-interop.sh, CI job python-interop).
  • The server (A2A\Server\*), in the shape of the Python SDK's a2a.server: AgentExecutor, RequestContext (with isCancelled()), RequestContextBuilder, SimpleRequestContextBuilder, ActiveTask, EventConsumer, ActiveTaskRegistry, EventQueue, InMemoryEventQueue, TaskStore, InMemoryTaskStore, CopyingTaskStore, TaskManager, TaskUpdater, ResultAggregator, PushNotificationConfigStore (in-memory), RequestHandler, DefaultRequestHandler (a port of Python's DefaultRequestHandlerV2), ServerCallContext, OwnerResolver and IdGenerator/UuidGenerator.
  • The executor runs in a PHP Fiber: every enqueued event is checked, saved, published and streamed before the executor continues. TaskRunner / InlineTaskRunner decide where it runs, hold a per-task run lease and finish deferred work after the response.
  • QueueManager as a per-task event log with cancel flags and run leases, so separate PHP processes can subscribe to, stream and cancel the same task: InMemoryQueueManager and PdoQueueManager (SQLite, PostgreSQL, MySQL).
  • PdoTaskStore (SQLite, PostgreSQL, MySQL; owner scoping, keyset pagination), brought forward from phase 5.
  • PSR-15 handlers: JsonRpcDispatcher, RestDispatcher (with tenant prefixes), AgentCardHandler (Cache-Control, ETag, Last-Modified, 304), Router, Routes; ResponseEmitter (per-event SSE flushing, disconnect detection, background work after the response), ServerRequestFactory, Sse\SseStream, DefaultServerCallContextBuilder.
  • examples/hello-world (a port of the Python SDK's sample agent) and tck/sut-agent.php.
  • Conformance: the official A2A TCK passes at the MUST (137/137), SHOULD and MAY levels over JSON-RPC and HTTP+JSON (scripts/run-tck.sh, CI job tck), and the official Python SDK client works against the PHP server over both transports.

Changed

  • TaskNotCancelableError maps to HTTP 409 (the A2A TCK's CORE-CANCEL-002; the released v1.0.0 spec table and the Python SDK say 400). The ErrorInfo reason is unchanged.
  • examples/call-an-agent.php, shown on the docs site and run in CI.

Changed

  • Requires php-http/discovery (finds a PSR-18 client when none is given) and psr/http-factory ^1.1.