Build1 publisherNot yet confirmed elsewhere3 min readPublished
The A2A card said 0.0.0.0:8080, and the only caller it broke was Google's
One line of Google ADK turns an agent into an A2A server. Deployed to Cloud Run and called from AWS and Azure, it advertised the address it binds instead of the address anyone can dial.
The Engineer · Build desk
What happened
- One research brief, three agents: ADK on Cloud Run, Strands on Bedrock AgentCore, Agent Framework on Container Apps, with a coordinator scoring all three answers.
- The deployed ADK agent's card, fetched over public HTTPS, reported a null top-level url and an interface at http://0.0.0.0:8080.
- An artifacts-only client got an empty string back from the Azure agent, with no error and no timeout.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A test suite that runs on one machine cannot detect this class of bug at all, because the bind address and the dial address only diverge once something else is doing the dialling.
- decision Anyone integrating A2A agents now has to decide whether to trust a published interface URL or to overwrite it with the address they resolved the card from.
- exposure A coordinator that scores answers has no way to tell a bad answer from a reply it failed to read, so client-side field choices show up as agent quality.
- contradiction Two vendors' executors put the reply in different places and both look conformant, which pushes the reconciliation cost onto every client instead of either server.
A bind address and a dial address are two different facts that happen to be the same string on a laptop [7]. That coincidence is the whole mechanism. `to_a2a(agent, host, port)` writes host and port straight into the interface URL the agent card advertises, and on Cloud Run the process binds `0.0.0.0:8080`, so a public HTTPS service publishes unroutable plaintext as its address [6][5]. A2A callers resolve that card at `/.well-known/agent-card.json` and dial what it says, over JSON-RPC, at protocol version 1.0 [4]. Local tests cannot see the defect, because locally the card is right.
The Strands agent on Bedrock AgentCore and the Agent Framework agent on Container Apps both take a `PUBLIC_URL` and advertise that [8]. Count the deployments: of three agents speaking the same protocol version, one publishes the address it listens on and two publish the address a stranger can reach [16]. That is an argument-list difference, not a verdict on frameworks, and it surfaces only when the resolver is somebody else.
The sharper detail is who found it. The all-first-party pairing, ADK client against ADK server, is the one hop that cannot complete, because both halves pass Google's own tests on the laptop identity [1]. When the client does fail, it fails at the wrong layer: `RemoteA2aAgent`'s handler assumes every `A2AClientError` carries a status code, which a transport failure does not, so the operator receives `AttributeError: 'A2AClientError' object has no attribute 'status_code'` while `All connection attempts failed` lands on a separate log line [9]. One defect sends the client somewhere unroutable, the second deletes the evidence of where it went [9].
Reply placement is the same species of problem with a worse failure mode. ADK's executor attaches the answer as a task artifact and also leaves a copy in task history [11]. Microsoft's `A2AExecutor` drives the full task lifecycle and leaves the answer only in history, artifacts empty [12]. A client that reads artifacts alone is the obvious implementation, works against ADK, and returns an empty string against Agent Framework: a successful call, no error, no timeout, no content [13]. Read every carrier the spec allows and ADK's reply now arrives twice [14]. So no single-carrier read is correct against both servers, and the both-carrier read is only correct with deduplication bolted on [17].
An earlier version of the same project hid that entirely. The agents returned exchange rates, and the parser indexed quotes by target currency, so the duplicate overwrote the original and the answer came out correct [15]. The payload shape was doing the deduplication, silently, for free, until the payload changed.
Both findings share a precondition, and it is not the framework. `to_a2a()` really is the shortest path from an `LlmAgent` to something another vendor's agent can call [3]. The path is short because it assumes the caller shares your process view. The author's operational advice is narrow and correct: fetch your own card after every deploy, and if you cannot fix the card, make your callers rewrite the interface URL after resolution rather than routing by it [10]. Which is to say that on today's A2A, the card is a hint, and the client owns the address.
What to watch
- Whether ADK gives to_a2a() a public URL argument or environment variable so the card advertises the dial address rather than the bind address.
- Whether the A2A spec narrows where a reply may live, or formally requires clients to read both artifacts and history and deduplicate.
- Whether RemoteA2aAgent's error path stops assuming every A2AClientError carries a status code, so transport failures name themselves.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence58
- Adoption18
- Hype gap+8
- Incentives32
- Confidence54
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
ADK's own client cannot reach ADK's own server once hosted; both halves pass Google's own tests because locally the two addresses are identical, so the one pairing that is entirely first-party code is the one that cannot complete a hop.
- [2]
A single research project answered one research brief with three agents on three clouds over one protocol and no stored credentials between them: Google ran an ADK agent on Cloud Run, AWS ran a Strands agent on Bedrock AgentCore, Azure ran an Agent Framework agent on Container Apps, and a coordinator asked all three the same question and scored what came back.
- [3]
The Agent Development Kit is Google's open source agent framework, model agnostic and deployment agnostic, running Gemini through Vertex AI or an API key, and to_a2a() turns an agent into an A2A server in one line; the author calls it the shortest path from an LlmAgent to something another vendor's agent can call.
- [4]
A2A is an open protocol in which an agent publishes a card at /.well-known/agent-card.json describing what it does and how to reach it, and speaks JSON-RPC over HTTP; this project runs A2A v1.0.
- [5]
Fetching the deployed agent's own card over public HTTPS returned {"url": null, "additionalInterfaces": [{"url": "http://0.0.0.0:8080", "protocolBinding": "JSONRPC"}]}, described by the author as a public HTTPS endpoint advertising unroutable plaintext.
- [6]
to_a2a(agent, host, port) writes host:port straight into the card's interface URL, and on Cloud Run the process binds 0.0.0.0:8080, so that is what the card says.
- [7]
The defect cannot be reproduced locally: on a laptop the bind address and the dial address are the same string, which is how it survives into a deployment.
- [8]
The other two agents in the project take a PUBLIC_URL and advertise that, which the author describes as the behaviour ADK is missing.
- [9]
Having dialled 0.0.0.0:8080 and failed, RemoteA2aAgent raises AttributeError: 'A2AClientError' object has no attribute 'status_code', because the error handler assumes any A2AClientError carries a status code, which a transport failure does not; the real cause, All connection attempts failed, goes to a separate log line. The author calls it two defects compounding: the first sends the client somewhere unroutable, the second deletes the evidence of where it went.
- [10]
The author's advice for serving with to_a2a() today: fetch your own card after deploying, and if you cannot fix the card, make sure your callers rewrite the interface URL after resolution rather than routing by it.
- [11]
ADK's executor attaches the reply as a task artifact and also leaves a copy in task history.
- [12]
Microsoft's A2AExecutor drives the full task lifecycle and leaves the reply only in history, with artifacts empty.
- [13]
A client that reads artifacts alone, which the author calls the obvious implementation and one that works perfectly against ADK, returns an empty string against Agent Framework: not an error, not a timeout, but a successful call with no content.
- [14]
Fixing the empty-reply case by reading every carrier the spec allows means ADK's reply then arrives twice.
- [15]
In an earlier version of the project the agents returned an exchange rate and the duplicate reply was invisible, because the parser indexed quotes by target currency so the second copy overwrote the first and the answer was correct.
- [16]
Of the three deployed agents in the project, one advertises its bind address in its card and two advertise a configured public dial address.
- [17]
No single-carrier reply read is correct against both servers: artifacts-only silently returns nothing from Agent Framework, and reading all carriers double-counts ADK, so a correct client must read both and deduplicate.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toMix and Match: Serving an ADK Agent to AWS and Azure
1 article · August 23, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.