Why architecture matters here
The architecture matters because tenant isolation is a property that cannot be added later. Every other cross-cutting concern — logging, tracing, retries — can be retrofitted with middleware, imperfectly but usefully. Isolation cannot, because its correctness depends on every access path honouring it, and a retrofit only covers the paths you remembered. The one you forgot is the breach.
Consider what 'the tenant boundary' actually has to cover. There is the data plane: task state, artifacts, conversation history, memory, embeddings. The control plane: which agents exist, which skills they expose, which versions are pinned. The resource plane: rate limits, token budgets, concurrency slots. And the observability plane, which routinely leaks tenant data through error messages and span attributes even when the data plane is airtight — nobody thinks of a log line as an access path until a support engineer reads one tenant's prompt out of another tenant's error report.
The delegation chain is what makes A2A distinctive. In a request that fans out through four agents, the tenant context has four opportunities to be dropped and four opportunities to be forged. Dropping it is the safe failure — a request with no tenant should be rejected, loudly, at the next boundary. Forging is the dangerous one: if a downstream agent accepts a caller-supplied tenant field without verification, any compromised or buggy upstream agent becomes a universal tenant-switching device. The design has to make the second case structurally impossible, which means tenant assertions travel in a form the recipient can verify independently rather than as a plain field in a JSON body.
There is also a real architectural choice about isolation depth, and it is a spectrum rather than a binary. At one end everything is shared and separation is purely logical — one database with a tenant column, one agent pool — which is cheap and dense and rests every guarantee on application code being correct everywhere, forever. At the other end each tenant gets dedicated infrastructure, which is expensive and makes leakage nearly impossible because there is no shared substrate to leak through. Most systems land in between; the discipline is being explicit about which plane sits where rather than letting each service decide for itself.
The final pressure is regulatory. Data residency, the right to deletion, per-tenant encryption keys, and audit requirements all land on the same boundary. A tenant that requires its data to stay in a specific region needs its agents, its task state, and its model calls to respect that — which means the tenant context has to reach the routing layer, not just the storage layer. A tenant exercising deletion rights needs every artifact, every task record, and every cached embedding to be findable by tenant, which means the tenant dimension has to be in the storage key rather than merely in a filterable column. These requirements arrive after the architecture is set, and they are brutally expensive to satisfy if the tenant was an afterthought.
The architecture: every piece explained
Identity establishment happens exactly once, at the edge, and everything downstream depends on it being done properly. A request arrives carrying a credential — typically an OAuth token or an mTLS client certificate. The edge verifies the signature, checks issuer and audience and expiry, and extracts the tenant claim. That claim is the authoritative tenant for the request. It is not a header the client can set, not a query parameter, not a field in the request body. It comes from a signed token the client could not have forged, and it is bound to the request context at the boundary before any business logic runs.
Context propagation is the mechanism that carries the tenant through the delegation chain. The naive approach passes it as a header and trusts it, which works right up until an internal agent is compromised or buggy. The robust approach is to re-assert rather than inherit: when agent X delegates to agent Y on behalf of tenant A, X requests a scoped token — from a token service, via a delegation or exchange grant — that names both the acting agent and the tenant, and Y verifies that token independently. Y's authorization decision then rests on a claim it verified itself, not on a field X asserted. This is more machinery than a header, and it is the difference between isolation that holds under a compromised internal service and isolation that does not.
Card resolution becomes a per-tenant operation. The agent card is not a static artifact when skills gate on entitlement: tenant A on the premium tier sees the advanced analysis skill; tenant B on the basic tier does not, and must not even learn it exists, since the card is an information disclosure surface as much as a capability advertisement. The resolution path therefore takes the tenant as an input and returns a filtered view, and the cache in front of it must be keyed by tenant. A card cache keyed only by agent id is a cross-tenant disclosure bug with a performance optimization wrapped around it — and it will be invisible until two tenants on different tiers hit the same cache entry.
Task identity and storage need the tenant in the key rather than beside it. A task id that is globally unique but not tenant-namespaced means a client that guesses or obtains another tenant's task id can potentially fetch it, and now your isolation rests entirely on every read path remembering to check ownership. Making the tenant part of the composite key — so that a lookup without the right tenant simply does not find the row, rather than finding it and then being denied — converts a policy check into a structural impossibility. The same applies to artifacts, streaming subscriptions, and push endpoints. Row-level security in the datastore is a strong second layer here, because it enforces the predicate at the engine rather than trusting each query to include it.
Quota and rate limiting are per-tenant buckets, sized from the contract. A global limiter protects the service from total overload and does nothing about interference — tenant A can consume the entire global budget and tenant B sees throttling it did not cause. Per-tenant buckets mean A's runaway loop exhausts A's budget and stops, while B proceeds untouched. The buckets need to cover every scarce resource the tenant can consume: requests per second, concurrent tasks, model tokens, tool invocations, and storage. Token budget is the one people forget, and it is frequently the most expensive, because a single agent loop with a large context can burn more money in an hour than a million cheap requests.
End-to-end flow
Trace a request from tenant A through a three-agent chain. A user at tenant A asks the orchestrator agent to summarize their quarterly contracts. The request arrives at the edge carrying an OAuth token issued by tenant A's identity provider.
The edge verifies the token's signature against the issuer's keys, confirms audience and expiry, and extracts the tenant claim: tenant_a. This is bound to the request context. The edge also consults the per-tenant rate limiter — tenant A's bucket, not a global one — and admits the request. Any subsequent component that needs the tenant reads it from this context, and no component reads it from anything the client sent.
The orchestrator resolves the contract-analysis agent's card. The resolution is parameterized by tenant, so the returned card reflects tenant A's entitlements: it includes the bulk-summarize skill because A is on a tier that has it, and it pins the skill version A's contract specifies. The cache serving this lookup is keyed on (agent_id, tenant, version), so tenant B's differently-shaped card cannot be served from it.
The orchestrator now delegates. It does not simply forward the user's token — that would grant the downstream agent the full scope of the user's credential, far more than the subtask needs. Instead it performs a token exchange, requesting a scoped token that names the orchestrator as the actor, tenant A as the tenant, and a narrow scope covering only contract reads. The contract-analysis agent receives this token, verifies it independently against the token service's keys, and derives tenant A from the claim it just verified — not from a header the orchestrator asserted. The task it creates is keyed with the tenant as part of the composite id, so it lives in a namespace tenant B cannot address even by guessing.
The analysis agent reads from a shared vector store. The query carries a tenant predicate, and the store enforces row-level security independently, so even a query that omitted the predicate would return nothing rather than everything — defence in depth, because the application-level filter is the one that will eventually be forgotten in some new code path. It calls the model, and the tokens consumed decrement tenant A's token budget specifically. It writes an artifact, tenant-namespaced. Every hop emits an audit record naming the tenant, the acting agent, the scope exercised, and the decision. When the result flows back and the orchestrator streams it to the user, the final response carries only tenant A's data, and the audit trail can prove it — which is the property that turns 'we believe we are isolated' into something you can put in front of a regulator.