Back to Knowledge Base
WhitepaperA2AAgent-to-AgentMulti-AgentMCPModel Context ProtocolTool CallingOrchestrationEvaluator-ExecutorAdapter PatternAI Security

A2A Protocol and Multi-Agent Architectures — Whitepaper for CTOs

ÁZ&A
Ádám Zsolt & AIMY
||27 min read

Executive Summary

AI agents are useful on their own — but exponentially more powerful together. Just as microservices revolutionized software development, Agent-to-Agent (A2A) communication is revolutionizing AI applications. But how should agents talk to each other? Through what protocol? Who decides when two agents give contradictory suggestions?

This whitepaper examines three layers:

  1. The A2A protocol — the open standard published by Google in 2025, aiming to become the HTTP of inter-agent communication
  2. MCP (Model Context Protocol) — Anthropic's standard defining the connection between agents and external tools
  3. Practical multi-agent architecture — how a production system works where multiple agent roles collaborate

The whitepaper presents the principles through a real, multi-tenant SaaS system implementation — with code snippets, configuration examples, and architectural decisions.


1. The Problem: Why Do Agents Need to "Talk"?

1.1 Limitations of a Standalone Agent

A typical AI agent does one thing well: responds to customers, analyzes emails, or generates tasks. But in business reality, tasks are not isolated:

Incoming email: "Hi! I'd like to book my usual appointment for tomorrow."

Required actions:
  1. Email interpretation (intent detection)         → Language agent
  2. Customer identification (CRM lookup)            → CRM agent
  3. Finding available time slot (calendar)           → Calendar agent
  4. Creating the booking                            → Booking agent
  5. Sending confirmation email                      → Communication agent
  6. Updating revenue prediction                     → Analytics agent

If a single "super-agent" does all of this, it:

  • Requires an overly complex system prompt (10+ pages of instructions)
  • Doesn't scale (one agent can't know everything)
  • Isn't modular (you can't swap out the calendar agent when a better one arrives)
  • Is hard to debug (which part failed out of the 6?)

1.2 The Microservice Analogy

A multi-agent system does for AI exactly what microservices did for backends:

Monolithic App Microservice AI Monolith Multi-Agent
One large applicationSmall, independent servicesOne "all-knowing" agentSpecialized agents
One deploy = everythingIndependent deployOne system promptAgent-specific prompt
Hard to debugService-level logging"Why did it say that?"Agent-level audit trail
Hard to scaleHorizontal scalingToken limitAgent-level resources

1.3 But How Should They Communicate?

With 5 agents, they need to coordinate somehow. Two key questions:

  1. Protocol: What format should they use to send messages to each other?
  2. Orchestration: Who decides when which agent acts?

A2A and MCP answer this — but each answers something different.


2. The A2A Protocol — Google's Answer

2.1 What Is A2A?

The Agent-to-Agent (A2A) protocol is the open standard published by Google in April 2025. Its goal: to enable AI agents from different developers, running on different platforms to communicate in a standardized way.

A2A aims to be the "HTTP" of inter-agent communication — just as REST APIs standardized service-to-service communication, A2A standardizes agent-to-agent communication.

2.2 A2A Core Concepts

Concept Meaning Analogy
Agent CardJSON description of the agent's "business card": capabilities, endpoint, authenticationOpenAPI spec
TaskA unit of work that the agent performsHTTP Request/Response
MessageWhat the agent sends/receives within a taskHTTP Body
PartA piece of the message (text, file, data)MIME part
ArtifactOutput produced by the agentResponse body
CapabilityOne of the agent's abilities (e.g., "can send email")API endpoint

2.3 The Agent Card — the Agent's "OpenAPI Spec"

Every A2A-compatible agent publishes an Agent Card (JSON):

{
  "name": "CRM Agent",
  "description": "Manages customer relationships, contacts, deals, and tasks",
  "url": "https://api.example.com/agents/crm",
  "version": "1.0.0",
  "capabilities": [
    {
      "name": "lookup_customer",
      "description": "Find customer by name, email, or phone",
      "inputSchema": {
        "type": "object",
        "properties": {
          "query": { "type": "string" }
        }
      }
    }
  ],
  "authentication": {
    "type": "bearer",
    "tokenUrl": "https://auth.example.com/token"
  },
  "protocol": "a2a/1.0"
}

This enables an unknown agent to reach another: it reads the Agent Card, understands the capabilities, and sends a request in a standardized format.

2.4 The A2A Task Lifecycle

Client Agent                          Server Agent
     │                                      │
     │── POST /tasks/create ──────────────▶ │
     │   { capability: "lookup_customer",   │
     │     input: { query: "Kiss Anna" } }  │
     │                                      │
     │◀── 202 Accepted ───────────────────  │
     │   { taskId: "task-42",               │
     │     status: "in_progress" }          │
     │                                      │
     │── GET /tasks/task-42 ──────────────▶ │
     │                                      │
     │◀── 200 OK ─────────────────────────  │
     │   { status: "completed",             │
     │     artifacts: [                     │
     │       { type: "customer_data",       │
     │         content: { ... } }           │
     │     ] }                              │
     │                                      │

Task states: pendingin_progresscompleted / failed / cancelled

2.5 Streaming and Long-Running Tasks

A2A supports Server-Sent Events (SSE) based streaming for long-running tasks:

Client Agent                          Server Agent
     │                                      │
     │── POST /tasks/create ──────────────▶ │
     │   { capability: "analyze_emails",    │
     │     streaming: true }                │
     │                                      │
     │◀── SSE stream ─────────────────────  │
     │   event: status                      │
     │   data: { status: "analyzing" }      │
     │                                      │
     │   event: progress                    │
     │   data: { percent: 45 }              │
     │                                      │
     │   event: artifact                    │
     │   data: { type: "summary", ... }     │
     │                                      │
     │   event: complete                    │
     │   data: { taskId: "task-42" }        │
     │                                      │

2.6 How Is It Different From REST API?

Aspect Traditional REST API A2A Protocol
DiscoveryOpenAPI / Swagger (manual)Agent Card (automatic)
CommunicationSynchronous request-responseAsynchronous task lifecycle
IntelligenceClient knows what to requestAgent decides how to execute
StreamingSeparate implementation (WebSocket, SSE)Built-in SSE support
AuthenticationCustom per-APIStandardized token-exchange
State managementStateless (or custom session)Task-level state

The key difference: with a REST API, the client precisely tells the server what to do. With A2A, the client agent expresses a goal, and the server agent decides how to accomplish it.


3. MCP — Between the Agent and the World

3.1 MCP vs. A2A: Not Competitors, But Complementary

MCP (Model Context Protocol) A2A (Agent-to-Agent)
Published byAnthropic (Nov 2024)Google (Apr 2025)
What does it connect?Agent ↔ Tool/Data sourceAgent ↔ Agent
AnalogyUSB cable (device connection)TCP/IP (network communication)
DirectionAgent uses a toolTwo agents collaborate
ExampleGmail tool, Calendar toolCRM agent → Email agent

Using both protocols together is where the real power lies:

┌─────────────────────────────────────────────┐
│           Orchestrator Agent                 │
│                                              │
│  ┌──────────┐    A2A    ┌──────────────┐    │
│  │ CRM Agent│◄────────►│ Email Agent   │    │
│  └────┬─────┘           └──────┬───────┘    │
│       │ MCP                    │ MCP         │
│  ┌────▼─────┐           ┌──────▼───────┐    │
│  │ CRM DB   │           │ Gmail API    │    │
│  │ (tool)   │           │ (tool)       │    │
│  └──────────┘           └──────────────┘    │
└─────────────────────────────────────────────┘

MCP: agent ↔ external tool (Gmail, Calendar, CRM)
A2A: agent ↔ agent (CRM agent ↔ Email agent)

3.2 In Practice: MCP Tools as Agent Capabilities

In a real system, MCP tools often define the agent's capabilities. Connectors (Gmail, Calendar, billing systems) publish MCP tools that the agent can use:

// Gmail connector — MCP tool registration
getTools() {
  return [
    {
      definition: {
        type: 'function',
        function: {
          name: 'gmail_send',
          description: 'Send email via Gmail',
          parameters: {
            properties: {
              to: { type: 'string', description: 'Recipient email' },
              subject: { type: 'string', description: 'Subject' },
              body: { type: 'string', description: 'Message body' },
              cc: { type: 'string', description: 'CC' }
            },
            required: ['to', 'subject', 'body']
          }
        }
      },
      execute: async (args) => this._mcpSendEmail(args)
    }
  ];
}

These tools register dynamically — the agent discovers at runtime which MCP tools are available:

async function getProviderMCPTools(providerId) {
  const configs = await prisma.connectorConfig.findMany({
    where: { providerId, isEnabled: true }
  });
  
  for (const config of configs) {
    const connector = new ConnectorClass(providerId);
    const tools = connector.getTools();
  }
}

This means: when a provider enables the Gmail connector, the AI agent automatically "learns" that it can now send emails. If disabled, the capability disappears. No redeployment, no code changes.


4. Multi-Agent Architecture: The Reality

4.1 Orchestration Patterns

Three main approaches exist for how multiple agents work together:

1. Hierarchical (Orchestrator Pattern)

         ┌──────────────┐
         │ Orchestrator  │  ← Main decision maker
         │    Agent      │
         └──────┬───────┘
          ┌─────┼──────┐
          ▼     ▼      ▼
       ┌────┐┌────┐┌────┐
       │CRM ││Mail││Cal │  ← Specialized agents
       └────┘└────┘└────┘
  • The orchestrator delegates to specialized agents
  • Advantage: clear control, easy debugging
  • Disadvantage: the orchestrator is an SPF (single point of failure)

2. Market (Marketplace/Auction Pattern)

       ┌────┐ ┌────┐ ┌────┐
       │ A1 │ │ A2 │ │ A3 │
       └─┬──┘ └─┬──┘ └─┬──┘
         │      │      │
         ▼      ▼      ▼
       ┌──────────────────┐
       │   Task Board      │  ← Shared task board
       └──────────────────┘
  • Tasks appear on a shared "marketplace"
  • Agents "bid" — the most suitable one takes it
  • Advantage: decentralized, scalable
  • Disadvantage: complex coordination, slower

3. Pipeline (Chain Pattern)

  Input → [Agent 1] → [Agent 2] → [Agent 3] → Output
           Analysis     Decision    Execution
  • Fixed order, each agent performs its own task
  • Advantage: simple, predictable
  • Disadvantage: not flexible, hard to backtrack

4.2 Practical Implementation: The Evaluator-Executor Pattern

Real production systems often implement a pragmatic variant of the hierarchical model, where agents aren't physically separate but are logical agent roles within a single system:

┌──────────────────────────────────────────────────────┐
│                    Agent Loop                         │
│                                                       │
│  ┌─── Trigger Layer ───┐                              │
│  │ Event: email arrived │                              │
│  │ Scheduled: 15min tick│                              │
│  └──────────┬──────────┘                              │
│             ▼                                         │
│  ┌─── Evaluator Agent ──┐   (LLM: GPT-4o-mini, T=0.3)│
│  │ "What happened?"      │                              │
│  │ "What should be done?"│                              │
│  │ "How confident am I?" │                              │
│  └──────────┬──────────┘                              │
│             ▼                                         │
│  ┌─── Autonomy Gate ────┐                              │
│  │ notify_only → log     │                              │
│  │ suggest_and_wait → ⏳ │                              │
│  │ act_and_report → ▼    │                              │
│  └──────────┬──────────┘                              │
│             ▼                                         │
│  ┌─── Executor Agent ───┐                              │
│  │ create_task           │                              │
│  │ send_email (MCP)      │                              │
│  │ create_event (MCP)    │                              │
│  │ update_contact        │                              │
│  │ flag_attention        │                              │
│  └──────────────────────┘                              │
└──────────────────────────────────────────────────────┘

This is the Evaluator-Executor pattern:

  • The Evaluator is the "brain": LLM-based, with low temperature (0.3) for deterministic decisions
  • The Executor is the "hand": deterministic code that carries out the Evaluator's decisions
  • The Autonomy Gate is the "supervisor": decides whether a decision needs human approval

4.3 The Evaluator Agent in Detail

The Evaluator is the system's most critical component. It operates in two modes:

Event mode — when something happens (email, webhook, CRM event):

const EVAL_CONFIG = {
  provider: 'openai',
  model: 'gpt-4o-mini',
  maxTokens: 1024,
  temperature: 0.3
};

async function evaluateEvent(providerId, triggerData) {
  const eventContext = await buildEventContext(
    providerId,
    triggerData.source,
    triggerData.eventType,
    triggerData.eventId,
    triggerData.eventData
  );
  
  const ragContext = await retrieveRAGContext(providerId, eventContext);
  const recentActions = await getRecentActions(providerId, 5);
  
  const response = await adapter.chat([
    { role: 'system', content: evaluatorPrompt },
    { role: 'user', content: contextMarkdown }
  ], EVAL_CONFIG);
  
  return parseEvalResponse(response.content);
}

Scheduled mode — proactive checks every 15 minutes:

async function evaluateScheduledTick(providerId) {
  const suggestions = await generateSuggestions(providerId);
  await expirePendingActions(providerId);
  const events = await getUpcomingEvents(providerId);
  const deals = await getDealsSummary(providerId);
  return evaluate(contextMarkdown);
}

The response is structured JSON:

{
  "actions": [
    {
      "type": "create_task",
      "params": {
        "title": "Follow-up: Anna Kiss 30+ day deal",
        "description": "The Premium package deal has been in NEGOTIATION for 35 days.",
        "priority": "HIGH",
        "dueDate": "2026-03-31"
      },
      "confidence": 0.85
    }
  ],
  "reasoning": "The stale deal is high-value, and overdue tasks require immediate attention.",
  "confidence": 0.88
}

4.4 The Executor Agent: MCP as "Hands"

The Executor doesn't think — it executes what the Evaluator decided. Every action handler is a clean, deterministic function:

async function execute(action, providerId) {
  switch (action.type) {
    case 'create_task':
      return executeCreateTask(action.params, providerId);
    case 'send_email':
      return executeMCPTool('gmail_send', action.params, providerId);
    case 'create_event':
      return executeMCPTool('calendar_create_event', action.params, providerId);
    case 'update_contact':
      return executeUpdateContact(action.params, providerId);
    case 'flag_attention':
      return executeFlagAttention(action.params, providerId);
  }
}

8 action types — each with precisely defined inputs and outputs:

Action Type MCP? Description
create_taskCRMNoCreate a task
create_dealCRMNoCreate a deal
log_activityCRMNoLog an activity
update_contactCRMNoUpdate a contact
send_notificationPushNoFirebase notification
send_emailMCPYesSend email via Gmail
create_eventMCPYesCreate a calendar event
flag_attentionAlertNoHigh-priority alert

4.5 The Third Agent: The Chat Agent

Alongside the Evaluator and Executor, there's a third "agent": the Chat Agent — the one the user interacts with directly:

async function sendMessage(providerId, userId, providerName, { conversationId, message }, settings) {
  const systemPrompt = buildSystemPrompt(providerName, customPrompt, knowledgeContext, mcpPromptSection);
  const ragResult = await retrieveRAGContext(providerId, message);
  
  while (iterations < 3) {
    response = await adapter.chat(llmMessages, { tools: allTools });
    if (!response.toolCalls?.length) break;
    
    for (const toolCall of response.toolCalls) {
      const result = await executeTool(toolCall.function.name, args, providerId);
      llmMessages.push({ role: 'tool', content: JSON.stringify(result) });
    }
  }
}

The Chat Agent uses a different model, different temperature, and different tool set than the Evaluator:

Property Chat Agent Evaluator Agent
ModelConfigurable (GPT-4o, Claude, Gemini)GPT-4o-mini (fixed)
Temperature0.7 (creative responses)0.3 (deterministic decisions)
ToolsCRM query + MCP (Gmail, Calendar)None — receives context only
OutputNatural language (streamed)Structured JSON
TriggerUser messageEvent or scheduled tick
Max tools/iter3 iterations, unlimited toolsNo tool calling

4.6 Inter-Agent Communication

The three agents don't directly "talk" to each other — they coordinate through a shared data layer:

                    ┌──────────────────┐
                    │   Knowledge Graph │
                    │   + CRM DB        │
                    │   + Action Log    │
                    └────────┬─────────┘
                             │
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
        ┌──────────┐  ┌──────────┐  ┌──────────┐
        │  Chat    │  │ Evaluator│  │ Executor │
        │  Agent   │  │  Agent   │  │  Agent   │
        └──────────┘  └──────────┘  └──────────┘
              ▲              │              │
              │              ▼              ▼
        ┌──────────┐  ┌──────────┐  ┌──────────┐
        │ User     │  │ BullMQ   │  │ MCP      │
        │ (SSE)    │  │ Queue    │  │ Tools    │
        └──────────┘  └──────────┘  └──────────┘

Communication methods:

  1. Event-based (BullMQ queue): The ingestion pipeline sends triggers to the agent-tasks queue → the Evaluator processes them
  2. Shared state (PostgreSQL): All three agents read/write the same data layer
  3. Implicit (RAG): The Chat Agent "sees" the Evaluator/Executor's previous actions via semantic search

This shared state model is simpler than A2A-style direct communication, and more reliable in production — no network latency between agents, no message loss risk.


5. Tool Calling as Proto-A2A

5.1 The Tool Calling Concept

LLM tool calling (function calling) is the seed of multi-agent communication. When an LLM "calls a tool," it essentially delegates to another system:

User: "What appointments do I have tomorrow?"

LLM thinking: "I need calendar data → calling get_upcoming_events tool"

Tool call:
  → get_upcoming_events({ days: 1 })
  ← [{ summary: "Haircut - Anna Kiss", start: "2026-03-31 10:00" }, ...]

LLM response: "You have one appointment tomorrow: 10:00 haircut with Anna Kiss."

5.2 The Multi-Iteration Tool Calling Loop

Our production system resolves tool calling in a maximum of 3 iterations:

Iteration 1:
  LLM: "Searching for Anna Kiss in CRM"
  → Tool: search_knowledge("Anna Kiss")
  ← Result: { contacts: [{ id: 15, name: "Anna Kiss", lifecycle: "LOYAL" }] }

Iteration 2:
  LLM: "Let me look at her deals and appointments"
  → Tool: get_entity_connections({ nodeId: "client_15" })
  ← Result: { deals: [...], appointments: [...], invoices: [...] }

Iteration 3:
  LLM: "I now have enough information to answer"
  → No tool call → natural language response

This is the ReAct (Reasoning + Acting) pattern: the LLM thinks, acts, observes the result, and thinks again.

5.3 The Dynamic Tool Registry

Tools are not static — they are detected at runtime based on active connectors:

async function getAllTools(providerId) {
  const crmTools = [
    { name: 'search_knowledge', ... },
    { name: 'get_recent_emails', ... },
    { name: 'get_upcoming_events', ... },
    { name: 'get_entity_connections', ... },
    { name: 'list_deals', ... },
    { name: 'get_dashboard_stats', ... },
    { name: 'create_task', ... },
    { name: 'create_deal', ... }
  ];
  
  const { definitions: mcpTools } = await getProviderMCPTools(providerId);
  return [...crmTools, ...mcpTools];
}

This is a simplified, pragmatic version of the A2A Agent Card concept: not an external JSON manifest, but runtime discovery. The concept is the same — the agent's capabilities expand dynamically.


6. The Adapter Pattern — Provider-Agnostic Agents

6.1 Why Is It Critical?

If the agent is tied to a single LLM provider, vendor lock-in occurs. The adapter pattern allows the agent's logic to be independent of the underlying LLM:

         ┌──────────────┐
         │ Agent Logic   │  ← Doesn't know which LLM runs behind it
         └──────┬───────┘
                │ adapter.chat(messages, options)
                ▼
         ┌──────────────┐
         │ Adapter Layer │  ← Strategy Pattern
         └──┬────┬────┬─┘
            │    │    │
            ▼    ▼    ▼
        OpenAI Claude Gemini

6.2 The Adapter Implementation

Every adapter implements the same interface. The challenge: the three LLM providers use different formats:

Property OpenAI Anthropic (Claude) Google (Gemini)
System messagerole: 'system' in message arraySeparate system parameterSeparate systemInstruction
Tool formattools: [{ function }]tools: [{ name, input_schema }]tools: [{ functionDeclarations }]
Tool responserole: 'tool' messagetool_result content blockfunctionResponse part
Streamingstream: true parameterstream() methodsendMessageStream()

The adapter layer transforms these into a single unified format:

class AnthropicAdapter extends BaseAdapter {
  async chat(messages, options = {}) {
    const systemMessages = messages.filter(m => m.role === 'system');
    const conversationMessages = messages.filter(m => m.role !== 'system');
    
    const anthropicTools = options.tools?.map(t => ({
      name: t.function.name,
      description: t.function.description,
      input_schema: t.function.parameters
    }));
    
    const response = await this.client.messages.create({
      model: this.model,
      system: systemMessages.map(m => m.content).join('\n'),
      messages: this._convertMessages(conversationMessages),
      tools: anthropicTools,
      max_tokens: options.maxTokens
    });
    
    return {
      content: response.content.find(c => c.type === 'text')?.text,
      toolCalls: response.content.filter(c => c.type === 'tool_use')
        .map(c => ({ id: c.id, function: { name: c.name, arguments: c.input } })),
      usage: { input: response.usage.input_tokens, output: response.usage.output_tokens }
    };
  }
}

6.3 Per-Tenant Model Selection

In the multi-tenant system, each provider can use a different LLM:

model AiSettings {
  providerId  Int     @unique
  provider    String  @default("openai")
  model       String  @default("gpt-4o-mini")
  temperature Decimal @default(0.7)
  maxTokens   Int     @default(2048)
  systemPrompt String?
}

One provider uses GPT-4o, another Claude, a third Gemini — same system, same agent logic. Only the adapter changes.


7. Security Architecture: Multi-Agent System Controls

7.1 Why Extra Security?

In a multi-agent system, the "AI can make mistakes" risk grows exponentially: if the Evaluator decides wrong, the Executor takes the wrong action, the MCP tool sends the wrong email. Preventing cascade failures is the system's most important property.

7.2 The Autonomy Gate — Three-Level Control

async function processAction(action, providerId, config) {
  if (config.blockedActions.includes(action.type)) return;
  if (config.allowedActions.length > 0 &&
      !config.allowedActions.includes(action.type)) return;

  const todayCount = await getTodayActionCount(providerId);
  if (todayCount >= config.dailyActionLimit) return;

  switch (config.autonomyLevel) {
    case 'notify_only':
      await logAction({ ...action, status: 'notified' });
      break;
      
    case 'suggest_and_wait':
      await logAction({ ...action, status: 'pending', expiresAt: +24h });
      await sendNotification(providerId, 'New AI suggestion');
      break;
      
    case 'act_and_report':
      const result = await executor.execute(action, providerId);
      await logAction({ ...action, status: 'executed', output: result });
      break;
  }
}

7.3 The Daily Limit Mechanism

Maximum 50 actions per day — this prevents a misconfigured agent from "going rogue" and sending thousands of emails or creating tasks.

7.4 The Audit Trail — Everything Gets Logged

Every agent action goes to the AiActionLog table:

Field Purpose
actionTypeWhat it intended to do (create_task, send_email, etc.)
descriptionHuman-readable description
inputInput parameters (JSON)
outputExecution result (JSON)
confidenceEvaluator's confidence level (0-1)
reasoningEvaluator's reasoning (natural language)
statuspending / executed / failed / notified / rejected / expired
triggerTypeWhat triggered it (event / scheduled)
triggerSourceWhere the trigger came from (gmail / calendar / crm / system)
approvedByWho approved it (if manual)
expiresAtWhen the approval window expires

This granularity enables:

  • Post-mortem analysis: Why did the AI send the wrong email?
  • Performance measurement: What percentage of actions were approved?
  • Optimization: 80% of actions with confidence below 0.6 were rejected → raise the threshold

7.5 The Evaluator's Maximum 3 Actions

The Evaluator can suggest at most 3 actions per evaluation. This prevents an "overzealous" AI:

function parseEvalResponse(content) {
  const parsed = JSON.parse(content);
  const validActions = parsed.actions.filter(a => 
    VALID_ACTION_TYPES.includes(a.type)
  );
  return validActions.slice(0, 3);
}

8. The Connector as an "External Agent"

8.1 The Connector as an A2A Analogy

Connectors (Gmail, Google Calendar, billing systems) essentially function as agents of external systems. They're not LLM-based, but their interaction pattern is A2A-like:

A2A Concept Connector Implementation
Agent CardgetTools() — the connector publishes its capabilities
TaskWebhook/poll event — the external system sends a "task"
ArtifactNormalizedEvent — the connector's structured output
CapabilityMCP tool — what the connector "can do"

8.2 The Normalized Event as Agent Message

Every connector converts external system data into a unified format:

{
  source: 'gmail',
  eventType: 'email.received',
  externalId: 'msg-abc123',
  timestamp: new Date(),
  entities: [
    { type: 'email', externalId: 'msg-abc123', label: 'Re: Appointment', content: '...' },
    { type: 'client', externalId: 'contact-456', label: 'Anna Kiss', properties: { email: 'anna@...' } }
  ],
  edges: [
    { fromExternalId: 'msg-abc123', toExternalId: 'contact-456', type: 'SENT_BY' }
  ],
  textContent: 'Email body for embedding',
  rawData: originalGmailPayload
}

This normalization is a pragmatic implementation of the A2A Part and Artifact concepts — the agent receives data in its own format and forwards it in a standardized format to the system.

8.3 The BaseConnector as "Agent Interface"

class BaseConnector {
  async handleWebhook(payload)   // Webhook → NormalizedEvent[]
  async poll(since)              // Incremental sync → NormalizedEvent[]
  async fullSync()               // Full sync → NormalizedEvent[]
  getTools()                     // → [{ definition, execute }]
  async authorize(authData)      // OAuth2 setup
  async refreshToken()           // Token refresh
}

This interface is the imperative equivalent of the A2A Agent Card: not a JSON manifest, but a code-level contract.


9. Future Vision: A2A in the Product

9.1 Today: Internal Multi-Agent System

The current architecture is a closed, internal multi-agent system: the Evaluator, Chat Agent, Executor, and Connectors communicate within the same infrastructure.

9.2 Tomorrow: A2A-Compatible Agents

The A2A protocol enables agents to communicate across system boundaries:

Current architecture:
  [AIMY Evaluator] ──shared DB──▶ [AIMY Executor] ──MCP──▶ [Gmail]

Future A2A architecture:
  [AIMY CRM Agent]
       │ A2A
       ▼
  [Partner Booking Agent]  ← Another company's agent
       │ A2A
       ▼
  [Payment Agent]          ← Third-party payment agent
       │ A2A
       ▼
  [Notification Agent]     ← Communication agent

Specific scenarios:

1. Cross-business booking — A client's medical AI assistant asks the salon's AI: "Anna Kiss is available tomorrow at 2:00 PM." The salon AI returns the available slot, both systems' calendars update.

2. Supply chain coordination — The inventory agent signals a shortage, the procurement agent orders from the supplier via A2A, the financial agent approves.

3. Client transfer between systems — The cosmetician's AI sends a "client card" to the hairdresser's AI, which suggests an appointment.

9.3 The Technical Transition

The transition from an internal multi-agent system to an A2A-compatible one is gradual:

Step What It Means Complexity
1. Agent Card publishingExisting tool definitions as Agent Card JSONLow
2. A2A endpointHTTP endpoint for the task lifecycleMedium
3. AuthenticationOAuth2 token-exchange between agentsMedium
4. DiscoveryAgent registry where agents find each otherHigh
5. Trust frameworkWhich agent to trust? To what extent?High

The biggest challenge isn't implementing the protocol — it's the trust framework: how do you decide that an unknown agent's request is legitimate?


10. Practical Decision Matrix for CTOs

10.1 When Do You Need Multi-Agent Architecture?

Signal Indication
System prompt > 5000 tokens and growing✅ Strong
Agent uses > 10 different tools✅ Strong
You want different LLM configs for different tasks✅ Strong
Tasks naturally separate (chat vs. background analysis)✅ Strong
1-2 simple tools, one type of task❌ Not needed
Entire system fits in < 3000 token system prompt❌ Not needed

10.2 When Do You Need A2A Protocol?

Signal Indication
Agents run at different organizations✅ Strong
You want to integrate a third party's agent✅ Strong
Your system is self-contained, no external communication❌ Not needed yet
Internal agents only, one infrastructure❌ Shared state is sufficient

10.3 The Technology Selection Decision Tree

Do you need multiple "intelligent" components in the system?
  │
  ├─ No (1 agent, 1-5 tools) → Single agent, tool calling
  │
  └─ Yes → Different tasks, different configurations?
              │
              ├─ Yes → Multi-agent, Evaluator-Executor pattern
              │           │
              │           └─ Are the agents within one infrastructure?
              │               │
              │               ├─ Yes → Shared state (DB + Queue)
              │               │
              │               └─ No → A2A protocol
              │
              └─ No → Single agent, modular tool set

11. Summary: Multi-Agent System Layers

┌──────────────────────────────────────────────────────────────┐
│  User (Chat / Push / Dashboard)                               │
├──────────────────────────────────────────────────────────────┤
│  Chat Agent (LLM, T=0.7, streaming, tool calling)             │
├──────────────────────────────────────────────────────────────┤
│  Evaluator Agent (GPT-4o-mini, T=0.3, structured JSON)        │
│  Trigger: event (webhook) + scheduled (15min tick)            │
├──────────────────────────────────────────────────────────────┤
│  Autonomy Gate (notify / suggest_and_wait / act_and_report)   │
│  Safety: daily limit (50), allowed/blocked actions, 24h expiry│
├──────────────────────────────────────────────────────────────┤
│  Executor Agent (deterministic, 8 action types)               │
│  CRM actions + MCP tools (Gmail, Calendar)                    │
├──────────────────────────────────────────────────────────────┤
│  Adapter Layer (OpenAI / Anthropic / Gemini)                  │
│  Per-tenant model selection, unified interface                │
├──────────────────────────────────────────────────────────────┤
│  Knowledge Layer                                              │
│  Knowledge Graph (pgvector) + RAG Pipeline + Connectors       │
├──────────────────────────────────────────────────────────────┤
│  Infrastructure                                               │
│  PostgreSQL + Redis + BullMQ + Firebase                       │
└──────────────────────────────────────────────────────────────┘

Key Takeaways

  1. Evaluator ≠ Executor: The decision-making agent and the executing agent must be separated. This enables approval workflows, audit trails, and different autonomy levels.

  2. MCP and A2A are complementary: MCP operates between the agent and tools (Gmail, Calendar), A2A between agents. Both are needed — at different levels.

  3. Shared state is simpler than direct communication: As long as agents are within one infrastructure, PostgreSQL + BullMQ is more reliable and simpler than direct agent-to-agent messaging.

  4. The dynamic tool registry is the seed of A2A: If the agent discovers capabilities at runtime, that's the same concept that A2A Agent Card formalizes. The transition is gradual.

  5. Security is not optional: Autonomy levels, daily limits, action filtering, 24-hour approval windows, full audit trail — without these, a multi-agent system is dangerous.

  6. Different LLM configs for different agents: The Chat Agent is more creative (T=0.7), the Evaluator more deterministic (T=0.3). The provider-agnostic adapter pattern enables each agent to use its optimal model.

  7. A2A is needed when agents leave the system: As long as it's internal agents, shared state is sufficient. When you need to communicate with other organizations' agents, A2A will be the common language.


Planning a Multi-Agent Architecture or AI Integration?

The Atlosz team helps design and implement inter-agent communication, the Evaluator-Executor pattern, MCP integrations, and security controls — tailored to your system.

Let's Discuss Your AI Strategy →