Learn A2A - Multi-tenancy & Version Negotiation (v1.0)
Series/Learn A2A/Episode 10
Episode 10 of 23

Learn A2A - Multi-tenancy & Version Negotiation (v1.0)

One agent serving many tenants with strict context isolation, and how clients and servers determine the same protocol version. Including migration strategies from v0.3 to v1.0 without downtime.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 9 your agent was secured with authentication and signed agent cards. But there's a practical problem we haven't touched: how does one agent instance serve dozens or hundreds of clients at once — bank A, marketplace B, logistics C — without mixing their data? And how can an agent written for v1.0 still talk to a client still on v0.3? This episode answers both, two features that are pillars of A2A v1.0's maturity.

This episode's roadmap: we dissect multi-tenancy — from the concept, to using tenant in the wire protocol, to data isolation — then version negotiation and migration strategies between versions.

Multi-tenancy: One Agent, Many Tenants

A tenant is a logical isolation unit — usually one organization or one project subscribed to the agent. In a multi-tenant model, one agent process serves many tenants at once, and each tenant sees itself as the only user of the service. Imagine a single "Sales Analysis" agent serving Bank A and Marketplace B: both call the same endpoint, but their tasks, messages, and results must never leak into each other.

Per-tenant context affects three things at once: state (which tasks are running), data (which datasets are accessible), and configuration (keys, quota limits, each tenant's policies). All three must be scoped to the tenant, not shared globally. Without this separation, one small bug could show Bank A's data to Marketplace B — the classic SaaS incident.

Tenant in the Wire Protocol

A2A v1.0 supports multi-tenancy explicitly: a JSON-RPC request can carry a tenant parameter that marks the task's owner. The server reads this value and ensures the entire task lifecycle runs in that tenant's context:

JSON-RPC request with a tenant context
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "message/send",
  "params": {
    "tenant": "acme-corp",
    "message": {
      "role": "user",
      "parts": [{ "kind": "text", "text": "Analisis tren penjualan Q3" }]
    }
  }
}

On the SDK side, the tenant isn't just a label in the payload — it becomes part of the task storage key. The server can use a task store that separates tasks by tenant, so a tasks/get for tenant A will never find a task belonging to tenant B:

PythonA per-tenant scoped task store
class TenantScopedTaskStore(InMemoryTaskStore):
    def __init__(self) -> None:
        self.tasks: dict[str, dict[str, Task]] = {}
 
    def _key(self, tenant: str, task_id: str) -> str:
        return f"{tenant}:{task_id}"
 
    def get_task(self, tenant: str, task_id: str) -> Task | None:
        return self.tasks.get(tenant, {}).get(task_id)

This pattern reinforces an important principle: the tenant is part of the task's identity, not secondary metadata. It's carried through tasks/get, tasks/cancel, and push notifications so every access path uses the same tenant lens.

Info

When building a multi-tenant agent, make the tenant part of every path: authentication (the token carries a tenant claim), task store (combined tenant and task id key), artifacts (stored in a tenant namespace), and monitoring (tenant label on metrics). If any path forgets it, that's where the leak will happen.

Data & Configuration Isolation per Tenant

Isolation isn't just about tasks — it covers the agent's entire data surface:

  • Tasks and artifacts. Storage is partitioned per tenant, including the final results saved in task history.
  • Integration credentials. Database, LLM, or storage API keys are stored per tenant, fetched from the secret store based on the tenant — not one global set.
  • Rate limits and quotas. Usage limits are computed per tenant so one greedy tenant doesn't hurt the others.
  • Policies. Partner whitelists, maximum task duration, and other settings are differentiated per tenant.

Two technical principles must be upheld. First, the tenant must be deterministic — the tenant value always comes from a trusted source (the authenticated token claim), not from task input that a client could manipulate. Second, default deny — without an explicit mapping, a task must not access another tenant's data.

Version Negotiation: Compatibility Without Drama

The A2A ecosystem evolves fast — from v0.1 to v1.0 within the span of a year — and not everyone can upgrade at once. Version negotiation is the mechanism by which clients and servers determine the same protocol version before a transaction begins, and A2A makes it an official part of the Agent Card.

Two negotiation points are used:

  • The Agent Card declares the protocol_version the server understands, and each supported_interfaces entry can list a version on its endpoint.
  • The client reads the card, picks the highest version it supports, then sends requests to the endpoint with the matching binding.
A v1.0 Agent Card that also serves v0.3
{
  "agentName": "Analisis Lead",
  "protocol_version": "1.0",
  "supported_interfaces": [
    { "protocol_binding": "JSONRPC", "url": "https://lead-agent.example.com/", "protocol_version": "1.0" },
    { "protocol_binding": "JSONRPC", "url": "https://lead-agent.example.com/v0.3", "protocol_version": "0.3" }
  ]
}

Because the negotiation happens at the card level, which can be cached, v0.3 and v1.0 clients can coexist calling the same agent — each to their version's endpoint. This is the back-compat contract that makes staged rollouts possible.

Migrating from v0.3 to v1.0

A2A v1.0 introduces breaking changes compared to v0.3 — method name adjustments (tasks/send became message/send), event structure changes, and a new discovery mechanism. Safe migration doesn't happen in a single day. The common staged strategy:

  1. Run a dual interface. During the transition, serve v0.3 and v1.0 side by side on different endpoints like the example card above.
  2. Use the SDK compatibility layer. Official SDKs provide compatibility modules like a2a.compat.v0_3 for old handlers, so agent logic is written once.
  3. Migrate clients one by one. Prioritize internal orchestrators, then external partners — monitor per-version metrics during the process.
  4. Set a deprecation policy. Announce the retirement schedule for the v0.3 endpoint, then deactivate it when traffic reaches zero.

Warning

Don't postpone migration forever with the excuse "it's still compatible". The longer two versions run in parallel, the higher the maintenance cost and the more clients hang on the old endpoint. Set a retirement date from day one of launching the new endpoint.

Conclusion

Episode 10 brings your agents to the multi-client production level: multi-tenancy with the tenant parameter as part of the task identity, per-tenant data and configuration isolation with deterministic tenant and default deny principles, then version negotiation via protocol_version on the Agent Card that lets old and new clients coexist, capped off with a staged, measurable v0.3-to-v1.0 migration strategy.

Here's the core takeaway:

  • tenant is a logical isolation unit; one agent serves many tenants without mixing data.
  • JSON-RPC requests carry tenant, and the task store uses a combined tenant plus task id as the key.
  • Isolation covers tasks, artifacts, credentials, rate limits, and policies — with deterministic tenants and default deny.
  • Version negotiation happens via protocol_version on the Agent Card and supported_interfaces.
  • Migrating from v0.3 to v1.0 uses a dual interface, the SDK compatibility layer, and a clear deprecation policy.

The more tenants and the more diverse the clients, one thing starts to feel expensive: the HTTP+JSON overhead on every request. In episode 11 we discuss the solution — gRPC Support — A2A's third binding with protobuf, the advantages of streaming and backpressure, and when gRPC is more appropriate than HTTP/JSON. See you there!

Learn A2A - Multi-tenancy & Version Negotiation (v1.0) | Learn A2A