Abstract. In June 2025, Google handed the Agent2Agent (A2A) protocol to the Linux Foundation. By its one-year mark in April 2026, more than 150 organizations were backing it, among them AWS, Microsoft, Cisco, Salesforce, SAP, and ServiceNow, and it now ships inside Azure AI Foundry, Amazon Bedrock AgentCore, and Google's own Agent Development Kit. A2A does for agent-to-agent handoffs what MCP did for tool calls: it gives independent agents a standard way to discover what another agent can do and hand it a task, over plain JSON-RPC, instead of every integration inventing its own wire format. That standardization is genuinely useful, and it repeats a pattern this blog has covered before: the same interoperability that makes a protocol adoptable also makes its metadata something every hop pays for, whether the task needs it or not. This post builds a token-level model of what a single A2A hop actually bills, an AgentCard fetch, a JSON-RPC and Task envelope, and a history array that many client implementations replay in full on every status poll, then measures how that tax compounds as a task runs longer and as an orchestrator fans a task out across more remote agents. The fix doesn't touch a model: cache the card, cap the history, trim the envelope. All data and charts in this post are illustrative, modeled from the public A2A specification, not measured production traces. Not derived from proprietary customer data. Sources cited throughout. The research question. A2A gives two independent agents, possibly built by different teams on different stacks, a shared way to talk. A client agent fetches a remote agent's AgentCard, a JSON document at /.well-known/agent-card.json describing its name, its skills, and how to reach it. The client then sends a message/send or message/stream JSON-RPC call carrying a Message built from one or more Part objects, and the server replies with a Task, an object with an id, a status, an optional history array of the messages exchanged so far, and any artifacts the work produced. If the task is long-running, the client polls tasks/get until the status moves to completed. None of that is unreasonable. It's also, piece for piece, the same shape of overhead this blog measured in MCP's tool-schema tax: a discovery document that describes far more than a given task needs, fetched more often than it changes, plus a wire format with fixed bytes on every call. A2A adds one thing MCP's tool-call overhead doesn't have: a history field on the Task object. The specification defines an optional historyLength parameter on task queries so a client can ask for only the most recent N messages; when a caller doesn't set it, many client and server implementations default to returning the task's history in full, which means a long-running task can end up re-transmitting its entire conversation on every single poll. The research question: what does a single A2A hop actually bill in tokens once the AgentCard fetch, the JSON-RPC and Task envelope, and unbounded history replay are counted, how does that overhead compound as a task's message count grows, and how much of it can be removed without touching either agent's model? Methodology. This is an analytical framework built from the public A2A specification's object shapes, not a measured benchmark against a specific vendor's implementation. Every token count below is an illustrative estimate, stated as an assumption, the same convention used throughout this blog for cost-modeling posts that aren't reporting a measured trace. Core assumptions used throughout: | Parameter | Value | Meaning | |---|---|---| | AgentCard size (uncached) | 3,100 tokens | 8 skills, each with name, description, tags, and examples, plus capabilities/provider/security metadata | | JSON-RPC + Task envelope | 180 tok/call (naive) vs. 110 tok/call (trimmed) | jsonrpc, id, method, params wrapper plus Task/TaskStatus fields repeated on every call | | History-array entry | 190 tok/message | Prior Message object (role, parts, messageId) re-sent inside history on each unbounded poll | | Actual message content | 150 tok/call | The instruction or result payload the call exists to carry | | Calls per hop | 1 message/send + 3 tasks/get polls | A representative short-lived task | | Blended input price | $3.00 / 1M tokens | Representative mid-tier orchestrator/agent pricing | | Orchestrated task | 3 remote-agent hops | An orchestrator fanning a task out to specialist agents, the same fan-out pattern Anthropic measured at roughly 15x a single chat turn's token cost | The central relationship: an uncached AgentCard is paid in full on every new hop instead of once per agent relationship, and an uncapped history array re-bills every prior message on every poll instead of once. Both are protocol-layer costs, separate from whatever the receiving agent's own model spends reasoning about the task, and both are fixable with a caching policy and a query parameter, not a smarter model. What an A2A hop actually bills. Chart 1: What an A2A hop actually bills. Per remote-agent call with an 8-skill AgentCard and 1 send plus 3 status polls, a naive hop with an uncached card and uncapped history bills 5,560 tokens: 3,100 for the AgentCard fetch, 720 for JSON-RPC and Task envelope, 1,140 for unbounded history replay, and 600 for actual message content. An optimized hop with a cached card and historyLength=1 bills 1,660 tokens: 50, 440, 570, and 600 respectively. Four things get billed on a single hop, and only one of them is the reason the hop exists. In this model, actual message content, the instruction sent and the result returned, is a flat 600 tokens across the four calls. Everything else is protocol overhead: the AgentCard fetch alone is more than five times that, if it isn't cached. Add the JSON-RPC and Task envelope repeated on every call, plus a history array that grows with every poll, and the naive hop bills 5,560 tokens to move 600 tokens of substance. That's not a defect in the protocol; it's what a synchronous discovery-plus-polling design costs when nothing gets cached or bounded, the same tradeoff MCP's tool-schema tax makes on the tool-calling side of the same architecture. The tax compounds with an orchestrator's fan-out. Chart 2: The A2A tax is fixable without touching a model. At 100,000 orchestrated tasks per month, a 3-hop fan-out to specialist agents, and $3.00 per 1M blended input tokens, a naive baseline with an uncached card and uncapped history costs $50.04 per 1,000 tasks. Caching the AgentCard only brings it to $22.59, a 54.9% reduction. Capping history with historyLength only brings it to $44.91, a 10.3% reduction. Doing both brings it to $14.94, a 70.1% reduction. | Configuration | Cost / 1,000 tasks | vs. naive | |---|---:|---:| | Naive: uncached card, uncapped history | $50.04 | baseline | | Cache the AgentCard only | $22.59 | −54.9% | | Cap history with historyLength only | $44.91 | −10.3% | | Both: cached card + capped history | $14.94 | −70.1% | For a short-lived task, caching the AgentCard is the bigger lever by a wide margin: it's a flat 3,100-token cost paid on every uncached hop regardless of how chatty the task gets, so removing it recovers more than half the tax immediately. Capping history barely moves the needle here, 10.3%, because a 4-call task hasn't accumulated much history yet to replay. That ordering flips as tasks run longer, which is the part a one-shot cost snapshot misses entirely. History replay compounds with every turn; the AgentCard doesn't. Chart 3: Unbounded history replay compounds with every turn. Each tasks/get poll without historyLength re-sends the whole conversation so far. At 1 turn, naive overhead is 3,620 tokens versus 500 optimized. At 4 turns, 6,320 versus 1,850. At 8 turns, 12,580 versus 3,650. At 12 turns, 21,880 versus 5,450. The naive line curves upward; the optimized line stays close to straight. This is the finding that matters most for any task that isn't a single quick round trip. An AgentCard fetch is a fixed, one-time cost per hop; it doesn't get worse the longer the task runs. Unbounded history replay is the opposite: every poll re-sends every message exchanged so far, so a task's protocol overhead grows roughly with the square of its message count, not linearly with it. At 4 turns the naive line and the optimized line are within 3.4x of each other. At 12 turns they're 4x apart and the gap is still widening, because the naive line is quadratic and the optimized one, capped at historyLength=1, stays close to linear. This is exactly the shape of tax this blog found in blind retry loops: a fixed-looking overhead that's actually a function of how long the interaction runs, and that quietly stops being a rounding error once agents start doing real multi-turn work, negotiation, clarification, partial results, rather than a single request and a single reply. Long-running A2A tasks, the kind the protocol's own async tasks/get polling model is built for, are precisely the tasks most exposed to this. Cold discovery serializes into the critical path. Chart 4: Cold AgentCard fetches serialize into the critical path. Illustrative task latency, sequential vs. cached/parallel discovery. A single agent, 1 hop, has P50 latency of 2.58 seconds and P95 of 3.10 seconds. A 3-agent fan-out with sequential card fetch has P50 of 3.04 seconds and P95 of 4.35 seconds. The same fan-out with cached and parallel fetch has P50 of 2.51 seconds and P95 of 2.95 seconds. The token tax has a latency twin. An orchestrator that fetches each remote agent's AgentCard fresh, in sequence, before it can send the first message pays that round trip as pure serialized latency on top of the actual work. In this model, a 3-agent fan-out with sequential, uncached discovery adds roughly half a second at P50 and over a second at P95 versus a single-hop baseline. Caching each agent's card after the first fetch and issuing the remaining fetches in parallel instead of one after another brings the fan-out's latency back below the single-hop number, because the agents' own work is what dominates once discovery stops being on the critical path. What this means for engineering teams. A2A's interoperability is the point, not the problem. The same standardized AgentCard and JSON-RPC envelope that let a Salesforce agent talk to a SAP agent without custom integration code are what make this overhead measurable and fixable in the first place. An unmeasured, bespoke integration would hide the same waste with no name for it. AgentCard caching is table stakes, not a nice-to-have. An agent's declared skills and capabilities don't change between requests; refetching them per task, or worse per message, is paying full price for a document that's almost always identical to the one fetched a minute ago. The specification supports standard HTTP caching headers on the AgentCard endpoint for exactly this reason. historyLength is easy to forget and expensive to skip. It's an optional parameter with a spec-defined default that's implementation-dependent. A client that never sets it can work correctly for months and still be paying a compounding, quadratic tax on every task that runs past a handful of turns. The tax scales with orchestration depth. Multi-agent fan-out already costs roughly 15x a single chat turn on model tokens alone; an unoptimized A2A layer adds a second multiplier on top of that, one that has nothing to do with how capable any of the agents are. This is an integration-layer fix, not a model or prompt change. Nothing here requires a different model, a smarter prompt, or a quality tradeoff. It's caching policy and a query parameter that most A2A client libraries already expose. Implementation framework. Step 1: Cache the AgentCard per agent, not per task. An agent's card changes when its skills change, not when a new task starts. Key the cache on the agent's URL and respect whatever Cache-Control or ETag headers the server sends; fall back to a conservative TTL (minutes, not seconds) if it sends none. Step 2: Always set historyLength on tasks/get. Decide what your orchestrator actually needs to make its next decision, usually the latest message and current status, not the full transcript, and request exactly that. Reconstruct full history from your own task store if you need it for logging or debugging; don't pay the remote agent to re-ship it on every poll. Step 3: Trim optional envelope fields you don't consume. Task, TaskStatus, and Message all carry optional fields (metadata, contextId, timestamps) that some SDKs populate by default whether or not the caller reads them. Confirm what your orchestrator actually parses and drop the rest at the transport layer. Step 4: Parallelize discovery across a fan-out. If an orchestrator is about to delegate to three specialist agents, fetch (or read from cache) all three AgentCards concurrently before sending any message/send calls, instead of discovering and delegating to each agent in sequence. import time class AgentCardCache: """Per-agent AgentCard cache with a conservative TTL fallback.""" def __init__(self, ttl_seconds=300): self.ttl = ttl_seconds self._store = {} # agent_url -> (card, fetched_at) def get(self, agent_url, fetch_fn): cached = self._store.get(agent_url) if cached and (time.time() - cached[1]) < self.ttl: return cached[0] card = fetch_fn(agent_url) # GET {agent_url}/.well-known/agent-card.json self._store[agent_url] = (card, time.time()) return card def poll_task(client, task_id, history_length=1): """Always bound history explicitly; never rely on an implementation's default.""" return client.tasks_get(id=task_id, historyLength=history_length) async def fan_out(orchestrator, agent_urls, task_payload, card_cache): """Discover all remote agents concurrently, then delegate to each.""" import asyncio cards = await asyncio.gather([ asyncio.to_thread(card_cache.get, url, orchestrator.fetch_card) for url in agent_urls ]) return await asyncio.gather([ orchestrator.message_send(card, task_payload) for card in cards ]) Step 5: Measure the protocol layer separately from the model layer. Log AgentCard-fetch tokens, envelope tokens, and history-replay tokens as their own line item, distinct from whatever tokens the receiving agent's model spends reasoning about the task. A dashboard that blends the two hides exactly the waste this post measures. What this doesn't solve. This model uses one representative AgentCard size, one call pattern, and a fixed price point; real deployments vary on all three; an agent with 30 skills or a task that runs to 40 turns will move these numbers, though the direction, cache the card and cap the history, doesn't change. It also doesn't cover push notifications, streaming via message/stream, or authenticated extended cards, all part of the spec and each with its own overhead shape. And it's silent on a different, non-token cost: an AgentCard is also a trust boundary. Fetching and acting on metadata from an agent you don't control has its own security surface, separate from what it costs in tokens, and caching a card doesn't make it safe to trust blindly. Conclusion. A2A did the hard part correctly: it gave independent agents a shared way to discover each other and hand off work, without every team inventing its own protocol. What it didn't do, because no protocol can, is stop that shared format from being billed in full on every hop by default. An AgentCard that hasn't changed since the last fetch, and a history array nobody asked to see in full, are the same category of waste this blog has measured in MCP's tool schemas and in blind retry loops: metadata a system pays for out of habit, not because a task needed it. The fix is a caching policy and a query parameter, and it doesn't touch either agent's model. Nadir's context optimization already strips redundant tool schemas and repeated boilerplate out of a request before it ships, the same instinct applied here to whatever an orchestrator has to read back into context when it reasons over a Task's history or an AgentCard's skill list; every non-streaming response still carries nadir_metadata.benchmark_comparison.savings_usd and a savings dashboard entry, so the protocol tax, cached or not, is a number a team can watch instead of a guess it inherits from a default it never set. Teams standing up A2A integrations this year are making the same choice MCP integrations made in 2025: cache what doesn't change, bound what can grow without limit, and measure the protocol layer as its own line item before it quietly becomes the largest one. Data and charts in this post are illustrative, modeled from the public A2A specification, not measured production traces. Sources: Agent2Agent (A2A) Protocol Official Specification, v0.3.0. Linux Foundation, "A2A Protocol Surpasses 150 Organizations, Lands in Major Cloud Platforms, and Sees Enterprise Production Use in First Year," April 2026. Anthropic Engineering, "How we built our multi-agent research system," June 13, 2025.