Skip to main content
a2a-symfony-bundle — cover

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 named Client services, 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:init creates the table explicitly — the schema never changes during a request.
  • Messenger: tasks sent with returnImmediately go to a real asynchronous transport, and sync transports are rejected for them. a2a:work handles recovery and webhook retries.
  • Optional native gRPC via a2a:grpc:serve on 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-postgres runs against PostgreSQL 18 in throwaway containers: reconnection, access isolation, conflicting updates, rollback, duplicate IDs and lease recovery.
  • make test-symfony repeats 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.