Skip to main content

ADR-011: Use Agent2Agent (A2A) Protocol for Agent Communication

Date: 2025-10-06 Status: Accepted Deciders: Development Team Tags: agents, protocol, collaboration

Context​

The IDE requires agent-to-agent communication for:

  • Multi-agent workflows (code generation + review)
  • Agent collaboration (research + implementation)
  • Sub-agent coordination (planning + execution)
  • Cross-system agent interaction
  • Enterprise-grade authentication

User Requirement: "it will also be agentic with agents and sub-agents and follow the Anthropic methodologies for MCP tools and workflows"

Decision​

We will use Google's Agent2Agent (A2A) Protocol (now managed by Linux Foundation) for agent-to-agent collaboration, complementing MCP for tool/data access.

Rationale​

Why A2A Protocol​

  1. Industry Standard: 150+ organizations supporting (Google, Atlassian, Box, Cohere, PayPal, Salesforce, SAP)
  2. Linux Foundation: Open governance (contributed June 2025)
  3. Complements MCP:
    • A2A: Agent collaboration
    • MCP: Tool and data access
  4. Built on Standards: HTTP, SSE, JSON-RPC
  5. Enterprise Security: Authentication and authorization
  6. Flexible Execution: Quick tasks to multi-day workflows
  7. Human-in-the-Loop: Support for user intervention

Agent Collaboration Scenarios​

  1. Code Generation → Review:

    • Agent 1 (LM Studio): Generate code
    • Agent 2 (Claude Code): Review and suggest improvements
  2. Research → Implementation:

    • Agent 1: Research best practices
    • Agent 2: Implement based on findings
    • Agent 3: Test implementation
  3. Planning → Execution:

    • Planning Agent: Break down task
    • Execution Agents: Parallel implementation
    • Coordinator Agent: Merge results

Architecture​

Protocol Integration​

A2A + MCP Together​

// Agent uses MCP for tools, A2A for collaboration
class Agent {
constructor(
private mcpClient: MCPClient, // Access tools/data
private a2aClient: A2AClient // Collaborate with agents
) {}

async executeTask(task: Task): Promise<Result> {
// Use MCP to access tools/resources
const context = await this.mcpClient.getResource('filesystem', task.filePath);

// Use A2A to collaborate with other agents
const peerInput = await this.a2aClient.requestFromAgent('code-reviewer', {
action: 'review',
code: context
});

// Combine and return
return this.processResult(context, peerInput);
}
}

Implementation​

A2A Server Setup​

// src/services/a2a-server.ts
import express from 'express';
import { A2AServer } from '@a2aproject/server';

class IDEAgentServer {
private app = express();
private a2aServer: A2AServer;
private agents: Map<string, Agent> = new Map();

async initialize(): Promise<void> {
this.a2aServer = new A2AServer({
agentId: 'az1ai-ide',
name: 'AZ1.AI IDE Agent System',
version: '0.1.0',
capabilities: {
code_generation: true,
code_review: true,
testing: true,
refactoring: true
}
});

// Register agents
this.registerAgent('code-generator', new CodeGeneratorAgent());
this.registerAgent('code-reviewer', new CodeReviewerAgent());
this.registerAgent('tester', new TesterAgent());

// Handle A2A requests
this.a2aServer.on('request', async (request) => {
const agent = this.agents.get(request.targetAgent);
if (!agent) {
return { error: 'Agent not found' };
}

return await agent.handleA2ARequest(request);
});

this.app.listen(3002, () => {
console.log('A2A Server running on port 3002');
});
}

registerAgent(name: string, agent: Agent): void {
this.agents.set(name, agent);
}
}

export const ideAgentServer = new IDEAgentServer();

A2A Client​

// src/services/a2a-client.ts
import { A2AClient } from '@a2aproject/client';

class IDEAgentClient {
private client: A2AClient;

constructor() {
this.client = new A2AClient({
endpoint: 'http://localhost:3002/a2a',
agentId: 'az1ai-ide-client',
authentication: {
type: 'oauth2',
tokenEndpoint: process.env.OAUTH_TOKEN_ENDPOINT,
clientId: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_CLIENT_SECRET
}
});
}

async requestFromAgent(
agentName: string,
request: AgentRequest
): Promise<AgentResponse> {
return await this.client.sendRequest({
targetAgent: agentName,
action: request.action,
payload: request.payload,
timeout: 30000, // 30 seconds
streamResponse: true
});
}

async broadcast(
request: AgentRequest,
targetAgents: string[]
): Promise<AgentResponse[]> {
const promises = targetAgents.map(agent =>
this.requestFromAgent(agent, request)
);

return await Promise.all(promises);
}
}

export const ideAgentClient = new IDEAgentClient();

Agent Implementation​

// src/agents/CodeGeneratorAgent.ts
import { ideAgentClient } from '../services/a2a-client';
import { mcpClient } from '../services/mcp-client';

export class CodeGeneratorAgent implements Agent {
async handleA2ARequest(request: A2ARequest): Promise<A2AResponse> {
const { action, payload } = request;

switch (action) {
case 'generate':
return await this.generateCode(payload);

case 'refactor':
return await this.refactorCode(payload);

default:
throw new Error(`Unknown action: ${action}`);
}
}

private async generateCode(payload: any): Promise<A2AResponse> {
const { prompt, context } = payload;

// Use MCP to get LM Studio model
const models = await mcpClient.callTool('lmstudio', 'lmstudio_list_models', {});
const model = models[0].id;

// Generate code using LM Studio via MCP
const code = await mcpClient.callTool('lmstudio', 'lmstudio_chat', {
model,
messages: [
{ role: 'system', content: 'You are a code generation expert.' },
{ role: 'user', content: prompt }
]
});

// Send to reviewer agent via A2A
const review = await ideAgentClient.requestFromAgent('code-reviewer', {
action: 'review',
payload: { code: code.content[0].text }
});

return {
success: true,
data: {
code: code.content[0].text,
review: review.data
}
};
}
}

Multi-Agent Workflow​

// src/workflows/multi-agent-workflow.ts
export class MultiAgentWorkflow {
async executeCodeGenerationWorkflow(prompt: string): Promise<Result> {
// Step 1: Planning agent breaks down task
const plan = await ideAgentClient.requestFromAgent('planner', {
action: 'plan',
payload: { prompt }
});

// Step 2: Parallel code generation
const codeResults = await ideAgentClient.broadcast(
{
action: 'generate',
payload: { tasks: plan.data.tasks }
},
['code-generator-1', 'code-generator-2']
);

// Step 3: Sequential review
const reviews = [];
for (const result of codeResults) {
const review = await ideAgentClient.requestFromAgent('code-reviewer', {
action: 'review',
payload: { code: result.data.code }
});
reviews.push(review);
}

// Step 4: Testing agent validates
const testResults = await ideAgentClient.requestFromAgent('tester', {
action: 'test',
payload: { code: codeResults.map(r => r.data.code) }
});

// Step 5: Coordinator merges results
return {
code: codeResults,
reviews,
tests: testResults
};
}
}

A2A Extensions (Custom Functionality)​

// Custom A2A extension for IDE-specific features
export class IDEAgentExtension {
async extendAgent(agentId: string, extension: Extension): Promise<void> {
await ideAgentClient.client.registerExtension({
agentId,
extensionName: extension.name,
capabilities: extension.capabilities,
handler: extension.handler
});
}
}

// Example: File system extension
await ideAgentExtension.extendAgent('code-generator', {
name: 'file-system-access',
capabilities: ['read', 'write', 'list'],
handler: async (request) => {
// Use OPFS or FoundationDB via MCP
const content = await mcpClient.getResource('filesystem', request.uri);
return { content };
}
});

Agent Workflows​

Sequential Workflow​

async function sequentialWorkflow(prompt: string): Promise<Result> {
// Step 1: Generate
const generated = await ideAgentClient.requestFromAgent('code-generator', {
action: 'generate',
payload: { prompt }
});

// Step 2: Review (uses output from step 1)
const reviewed = await ideAgentClient.requestFromAgent('code-reviewer', {
action: 'review',
payload: { code: generated.data.code }
});

// Step 3: Fix (uses feedback from step 2)
const fixed = await ideAgentClient.requestFromAgent('code-generator', {
action: 'fix',
payload: {
code: generated.data.code,
feedback: reviewed.data.feedback
}
});

return fixed;
}

Parallel Workflow​

async function parallelWorkflow(tasks: Task[]): Promise<Result[]> {
// Execute all tasks in parallel
const results = await ideAgentClient.broadcast(
{ action: 'generate', payload: { tasks } },
tasks.map((_, i) => `code-generator-${i}`)
);

return results;
}

Consensus Workflow​

async function consensusWorkflow(prompt: string): Promise<Result> {
// Get responses from multiple agents
const responses = await ideAgentClient.broadcast(
{ action: 'generate', payload: { prompt } },
['agent-1', 'agent-2', 'agent-3']
);

// Synthesizer agent creates consensus
const consensus = await ideAgentClient.requestFromAgent('synthesizer', {
action: 'synthesize',
payload: { responses }
});

return consensus;
}

Security​

Authentication​

// OAuth 2.0 for A2A authentication
const a2aClient = new A2AClient({
authentication: {
type: 'oauth2',
tokenEndpoint: 'https://auth.az1.ai/token',
clientId: process.env.A2A_CLIENT_ID,
clientSecret: process.env.A2A_CLIENT_SECRET,
scopes: ['agent:read', 'agent:write', 'agent:execute']
}
});

Permissions​

// Agent permission system
interface AgentPermissions {
allowedAgents: string[];
allowedActions: string[];
rateLimits: {
requestsPerMinute: number;
maxConcurrent: number;
};
}

async function requestWithPermissions(
agentName: string,
request: AgentRequest,
permissions: AgentPermissions
): Promise<AgentResponse> {
if (!permissions.allowedAgents.includes(agentName)) {
throw new Error('Agent access denied');
}

if (!permissions.allowedActions.includes(request.action)) {
throw new Error('Action not permitted');
}

return await ideAgentClient.requestFromAgent(agentName, request);
}

Protocol Comparison​

FeatureMCPA2A
PurposeTool/data accessAgent collaboration
Scopellm ↔ ToolsAgent ↔ Agent
PrimitivesTools, Resources, PromptsRequests, Responses, Events
StandardsJSON-RPCHTTP, SSE, JSON-RPC
AuthenticationOAuth 2.1OAuth 2.0
StreamingYes (HTTP Transport)Yes (SSE)
ProviderAnthropicGoogle → Linux Foundation

References​