Featured work
a2a-symfony-bundle
A2A 1.0.0 agents in Symfony — routes, the application's security layer, Doctrine storage, Messenger workers and optional gRPC, on top of a2a-php-sdk.
- PHP 8.4
- Symfony 7.4 / 8
- A2A 1.0.0
- Doctrine DBAL
- Symfony Messenger
- Symfony Security
- gRPC (OpenSwoole)
- Prometheus
- PostgreSQL 18
The problem
a2a-php-sdk gives PHP the protocol. A Symfony application still needs the wiring around it: routes, services, authentication through its own security layer, task storage shared by several processes, and background workers. Assembling the SDK’s server components by hand in every project is where mistakes creep in.
What the bundle adds
- One route import exposes JSON-RPC (
/a2a/rpc), REST (/a2a), SSE streaming and discovery at/.well-known/agent-card.json. - The agent is one Symfony service implementing
Executor. Outbound agents are injected as namedClientservices, each with its own TLS, deadlines and headers. - Authentication through a Bearer token map or an existing Symfony
AccessTokenHandlerInterface. Tasks are scoped by caller identity and tenant, identically on HTTP and gRPC. - Storage in files on a shared volume or in Doctrine DBAL.
a2a:storage:initcreates the table explicitly — the schema never changes during a request. - Messenger: tasks sent with
returnImmediatelygo to a real asynchronous transport, and sync transports are rejected for them.a2a:workhandles recovery and webhook retries. - Optional native gRPC via
a2a:grpc:serveon OpenSwoole, JSON logs, and persistent Prometheus counters and latency histograms.
a2a:
executor: App\Agent\MyExecutor
card_file: '%kernel.project_dir%/config/agent-card.json'
public_url: '%env(A2A_PUBLIC_URL)%'
storage:
driver: doctrine
connection: doctrine.dbal.default_connection
clients:
peer:
endpoint: '%env(A2A_PEER_ENDPOINT)%'
binding: jsonrpc
Checked on real infrastructure
make test-postgresruns against PostgreSQL 18 in throwaway containers: reconnection, access isolation, conflicting updates, rollback, duplicate IDs and lease recovery.make test-symfonyrepeats the tests and container compilation on Symfony 7.4 and 8.0; the lockfile covers 8.1.- Real TLS and mTLS calls and streaming through the named clients, with certificates generated inside the test container.
- The packaged ZIP is installed into a fresh project and runs discovery plus a JSON-RPC task without gRPC extensions.
- PHPStan level 8 and PSR-12. The bundle’s CI checks the Symfony integration; protocol conformance comes from the SDK’s official TCK run.
Shipping
Released on Packagist under Apache-2.0, current version v1.0.2: composer require vbcherepanov/a2a-symfony-bundle. The Flex recipe is in the official symfony/recipes-contrib (merged 2026-09-16): it registers the bundle, imports the routes and adds a commented example config. The route import works before A2A is configured, so a fresh app boots in dev and prod.
Design choices
- Schema creation is a command, not a side effect. An HTTP request that silently migrates a production table is a failure mode, not a convenience.
- Identity comes only from the authenticator. The same identity and tenant contract applies to HTTP and gRPC, so a task created over one transport is not visible to another caller over the other.