Skip to content

The Multi-Agent Landscape

The problem

Multi-agent systems today are tightly coupled. Adding a new agent means updating every caller that needs to know about it. Remove an agent, and consumers break silently. Scale to multiple teams, and coordination becomes the bottleneck.

# This is how most multi-agent systems work today
from team_a import summarizer
from team_b import translator
from team_c import sentiment_analyzer

# Every consumer hardcodes every dependency.
# New agent? Change every file. Agent removed? Runtime crash.
result = await summarizer.invoke(text)

This doesn't scale past a single team.

The landscape

Two protocols have emerged to address parts of this problem:

MCP (Model Context Protocol) connects LLMs to tools. It's the standard for giving a model access to external capabilities: file systems, APIs, databases. MCP is LLM-to-tool.

A2A (Agent-to-Agent) is Google's protocol for cross-organization agent federation. It defines how agents from different companies discover and invoke each other over HTTP. A2A is org-to-org.

Neither addresses what happens inside an organization, where dozens or hundreds of agents need to discover and invoke each other at runtime without direct coupling.

The gap

graph LR
    subgraph "LLM Tooling"
        LLM -->|MCP| Tools[Tools & Resources]
    end

    subgraph "Cross-Org Federation"
        OrgA[Org A Agents] <-->|A2A| OrgB[Org B Agents]
    end

    subgraph "Internal Fabric"
        A1[Agent] <-->|OAM| A2[Agent]
        A2 <-->|OAM| A3[Agent]
        A1 <-->|OAM| A3
        A4[Agent] <-->|OAM| A2
    end

    style Internal fill:#f0f7ff,stroke:#3b82f6

There's no LAN of agents, no internal discovery fabric where agents register themselves, publish typed contracts, and find each other at runtime without any caller knowing about any provider in advance.

How OAM fills this

OpenAgentMesh is the missing layer: a NATS-based protocol for agent-to-agent communication within an organization.

from openagentmesh import AgentMesh

mesh = AgentMesh("nats://mesh.company.com:4222")

# No imports from other teams. No hardcoded dependencies.
# Discover what's available right now.
catalog = await mesh.catalog(channel="nlp")

# Call an agent by name. The mesh handles routing.
result = await mesh.call("summarizer", {"text": doc, "max_length": 200})

What this gives you:

  • Runtime discovery: agents register on startup, deregister on shutdown. Consumers discover what's available right now, not what was available at deploy time.
  • Typed contracts: every agent publishes input/output JSON Schemas via Pydantic v2. Validation happens automatically at the mesh boundary.
  • Zero coupling: adding a new agent requires zero changes to existing code. Any consumer can discover and invoke it immediately.
  • NATS as shared bus: a single infrastructure component provides pub/sub, request/reply, load balancing (queue groups), and a KV store for the registry. No separate service registry, message queue, or load balancer.

The service mesh analogy

If you've worked with Istio, Linkerd, or MuleSoft, the pattern will feel familiar. OAM applies the service mesh concept, proven in enterprise infrastructure, to AI agent architectures.

A service mesh gives microservices discovery, load balancing, and observability without each service knowing about the network topology. OAM does the same for agents, with one difference: routing is based on semantic understanding (what the agent does, what it accepts) rather than network-level rules.

One architecture, not two modes

The same agent code runs against a local NATS server during development and a multi-region cluster in production. The only thing that changes is the connection string: AgentMesh() becomes AgentMesh("nats://mesh.company.com:4222").

For a deeper look at the technology choices, see Technology Stack.