All posts
Fundamentals

A2A, agents talking across the network

Every pattern so far runs agents in the same process. A2A, agent-to-agent, is for when they do not: agents on different machines, in different systems, talking over the network.

A2A is an HTTP client-server protocol. One side exposes an agent as a server; the other calls it as a client. The whole thing centres on two ideas: an agent card that says who an agent is, and a task request that runs it.

Two ideas: the agent card and the task

The agent card is an agent's introduction. It is a document, served at a well-known URL, that describes the agent: its name, what it does, what it can be asked. A client fetches the card first, to learn what it is talking to.

GET /.well-known/agent.json
{
  "name": "research-agent",
  "description": "Researches a topic and returns a summary",
  "capabilities": ["research", "summarisation"]
}

The task request and response is the actual work. The client sends a task, the server runs its agent on it, and returns the result. Ask the card who you are talking to, then send the task.

Client                                  Server
  --- GET agent card --------------->
  <-- "here's who I am" -------------
  --- POST task: "research X" ------->   (runs the agent)
  <-- task result: "summary..." -----

Two exchanges: discover, then delegate. That is the core of A2A.

A2A: the client fetches the agent card to learn who it is talking to, then sends a task to run.A2A: the client fetches the agent card to learn who it is talking to, then sends a task to run.

The server: exposing an agent

To make an agent callable from other systems, you expose it as an A2A server, which is two endpoints.

Serve the agent card, so callers can discover what this agent is.

@app.get("/.well-known/agent.json")
def agent_card():
    return {"name": "research-agent",
            "description": "Researches a topic and returns a summary"}

Handle task requests, so callers can run it.

@app.post("/tasks")
async def handle_task(request: TaskRequest):
    result = await agent.run(request.input)      # run our agent on the request
    return {"output": result.output}

The whole server is those two endpoints: advertise the agent, and run it on request. An adapter sits between the A2A-formatted request and your agent, pulling the user input out of the request, running the agent, and formatting the response back. Your agent does not change; you have wrapped it in a network interface.

The client: calling a remote agent

The client is the mirror image. It fetches a remote agent's card to learn its name and description, then sends it tasks.

The nice part is that a remote agent can be made to look exactly like a local one. A RemoteAgent fetches the card up front, so it arrives already knowing the remote agent's name and description, and then presents the same interface as any agent.

class RemoteAgent:
    def __init__(self, url):
        self.url = url
        info = self._load_agent_card()          # fetch the card at construction
        self.name = info["name"]
        self.description = info["description"]

    async def run(self, task: str) -> str:
        return await self._post_task(task)      # HTTP under the hood

Because a RemoteAgent has a name, a description and a run method, it drops straight into the patterns from the earlier posts. You can wrap it in AgentTool and let an orchestrator call it, or register it as a specialist to transfer to. The orchestrator cannot tell that this specialist lives on another machine, because the interface is identical. A2A hides the network behind the same agent interface everything else uses.

A RemoteAgent fetches the card, then presents the same interface as a local agent, so it drops into the earlier patterns.A RemoteAgent fetches the card, then presents the same interface as a local agent, so it drops into the earlier patterns.

What this unlocks, and the caution that comes with it

A2A turns single-machine multi-agent systems into networked ones. A specialist agent maintained by another team, or another company, becomes a component your orchestrator can call, discovered by its card and run by a task request. That is a large step in what agents can be assembled from.

The surface area grows by the same large step, and it is worth naming plainly. A server that exposes an agent to the network is exposing something that has tools, memory, and possibly code execution, to anyone who can reach it. A client that calls a remote agent is trusting a component it does not run. The security series takes both directions of that apart in detail, because a networked agent is a very different security proposition from one in your own process.

What to take from this

  • A2A lets agents on different machines talk over HTTP. It centres on two things: an agent card that advertises what an agent is, and a task request that runs it.
  • A server is two endpoints: serve the agent card, and handle task requests by running your agent. You wrap an existing agent in a network interface; the agent itself does not change.
  • A client fetches the card and sends tasks, and a RemoteAgent presents the same interface as a local one, so it drops into the agent-as-tool and transfer patterns. The network is hidden behind the agent interface.

The agent's construction is now complete: from a single loop to a networked system of specialists. Building it is one thing; knowing where it breaks is another, and a networked, multi-agent system opens its own security questions. The security series takes those on next.