n4nAI

When to use A2A instead of a shared agent framework

Practical guide to when to use A2A protocol vs a shared agent framework: actionable steps, code samples, and tradeoffs for LLM engineers.

n4n Team4 min read895 words

Audio narration

Coming soon — every post will get a voice note here.

When to use a2a protocol instead of cramming every agent into one shared framework is a question that appears the moment you scale past a single Python process. The decision is not about which LLM you call; it is about ownership, independent deployability, and what happens when one agent crashes. This guide lays out an ordered path to make that call without overthinking it.

The core distinction

A shared agent framework means your agents are objects or modules inside one runtime, sharing memory, types, and a single dependency tree. A2A (agent-to-agent) means each agent exposes a network endpoint and speaks a message contract. You pay serialization and latency costs, but you gain isolation.

If you cannot articulate why two pieces of logic need separate failure domains, you probably don’t need A2A yet.

Step 1: Map agent boundaries and team ownership

List every autonomous unit you plan to build. For each, note:

  • Which team owns it
  • What data it is allowed to touch
  • Whether it has a separate release cadence

If two agents are owned by the same team, share the same DB credentials, and deploy together, a shared framework is simpler. The moment one agent is owned by a different team or has stricter compliance scope, you have a boundary worth enforcing with a protocol.

When to use a2a protocol becomes clear when organizational boundaries mirror runtime boundaries.

Step 2: Assess deployment independence

Ask: can agent A ship a breaking change without re-testing agent B? If no, they are coupled; keep them in a monorepo with a shared framework. If yes, define an HTTP or message-queue contract.

A minimal A2A envelope does not need a standard body yet. Start with:

{
  "sender": "agent_pricing",
  "receiver": "agent_checkout",
  "task": "quote_cart",
  "payload": {"items": [{"sku": "X1", "qty": 2}]},
  "reply_to": "https://pricing.internal/a2a/inbox"
}

The receiver validates the task string and returns a structured response. No shared Python class required.

Step 3: Evaluate model access and runtime dependencies

Agents often differ in which models they call. One might use a cheap classifier; another needs a frontier reasoning model. If you standardize on a single inference gateway, that concern is centralized. Even if you route through one OpenAI-compatible endpoint that addresses 240+ models with automatic fallback, like n4n.ai, the agent coordination problem remains separate.

The point: model routing is not a reason to adopt A2A by itself. But if agent A runs on edge containers and agent B runs in a GPU cluster, a shared framework is physically impossible. That is a hard trigger for when to use a2a protocol.

Step 4: Define the message contract explicitly

A shared framework gives you compile-time type checks. A2A gives you a schema file. Write it down in JSON Schema or Protobuf. Example receiver in FastAPI:

from fastapi import FastAPI
from pydantic import BaseModel

class A2AMsg(BaseModel):
    sender: str
    receiver: str
    task: str
    payload: dict
    reply_to: str | None = None

app = FastAPI()

@app.post("/a2a/inbox")
async def inbox(msg: A2AMsg):
    if msg.task == "quote_cart":
        total = sum(i["qty"] * 10 for i in msg.payload["items"])
        return {"status": "ok", "result": {"total": total}}
    return {"status": "unknown_task"}

The sender can be a different language entirely. That is the win.

Step 5: Prototype with real failure modes

Most A2A designs die in retry handling. Build a sender that:

  1. Posts the message
  2. Expects a correlation id
  3. Times out after 5s
  4. Retries with idempotency key
import uuid, requests

def send_a2a(url, msg):
    msg["correlation_id"] = str(uuid.uuid4())
    msg["idempotency_key"] = msg["correlation_id"]
    for attempt in range(3):
        try:
            r = requests.post(url, json=msg, timeout=5)
            if r.status_code == 200:
                return r.json()
        except requests.Timeout:
            continue
    return {"status": "failed"}

If you skip this, you will get duplicate side effects. A shared framework call is just a function return; A2A is distributed systems.

Step 6: Instrument and trace

With a shared framework you get stack traces. With A2A you get network hops. Emit a trace id in every message and log both sides. Without this, debugging is guessing.

When to use a2a protocol should also consider your observability maturity. If you have no distributed tracing, the operational tax may outweigh the benefit for low-stakes agents.

Common pitfalls and tradeoffs

Schema drift. One team adds a field; the other doesn’t read it. Use a versioned path: /v1/a2a/inbox.

Latency. A function call is microseconds; a network call is milliseconds. If agent A calls agent B 50 times per request, keep them in-process.

Partial failure. Agent B processed the task but its response dropped. Design tasks to be idempotent or use a status endpoint.

Over-partitioning. Junior teams split agents by noun (“user agent”, “order agent”) with no real boundary. You get a distributed monolith.

Decision checklist

Use A2A when:

  • Agents are owned by different teams or deploy on different schedules
  • Runtime environments are heterogeneous (browser, server, edge)
  • Failure of one agent must not crash others
  • You need polyglot implementation (Rust sender, Python receiver)

Stick with a shared framework when:

  • All agents live in one repo and one process
  • Latency between agents is on the critical path
  • You lack tracing infrastructure
  • The boundary is hypothetical, not enforced by compliance or scale

When to use a2a protocol is ultimately a question of coupling vs isolation. Pick the side that matches your organizational reality, not the hype cycle. Build the smallest possible message contract, instrument it, and add agents only when the boundary proves real.

A minimal path to start

  1. Write down two agents and the one task that crosses them.
  2. Define a JSON envelope with sender, receiver, task, payload, reply_to.
  3. Implement the receiver as a plain HTTP endpoint.
  4. Add a correlation id and a 5s timeout in the sender.
  5. Run it across two processes on your laptop.
  6. Break the receiver deliberately; confirm the sender survives.

If that exercise feels natural, you have your answer. If it feels like bureaucracy for a single binary, delete the endpoints and import the module.

The protocol is a tool, not a mandate. Use it where the seam is real.

Tagsa2aagent-frameworksarchitectureguide

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All agent-to-agent (a2a) communication protocols posts →