A2A Communication
Last Updated: 2026-01-08
A2A (Agent-to-Agent) enables agents in separate memory spaces to communicate via dual-write messaging. Messages are stored in BOTH sender and receiver spaces—complete isolation with coordination.
Quick Start
// Send message (stored in BOTH agents' spaces)
await cortex.a2a.send({
from: "finance-agent",
to: "hr-agent",
message: "Budget approved for Q4 hiring",
importance: 85,
});
// HR agent receives it in their memory
// Note: getInbox() is planned - see /roadmap/a2a-communication-enhancements
// Current workaround: use memory.search() with filters
const inbox = await cortex.memory.search("hr-agent", "*", {
source: { type: "a2a", recipient: "hr-agent" },
limit: 10,
});
How Dual-Write Works
Each agent maintains complete isolation while still being able to coordinate. No shared state—just message passing.
A2A vs Hive Mode
| Feature | A2A (Collaboration Mode) | Hive Mode |
|---|---|---|
| Memory Spaces | Separate (one per agent) | Shared (multiple agents → one space) |
| Communication | Explicit messaging | Direct read/write |
| Isolation | Complete isolation | Participant tracking |
| Use Case | Autonomous agents, enterprise | Personal AI, MCP tools |
| Best For | Finance ↔ HR workflows | Cursor + Claude sharing |
- A2A: Autonomous agents needing complete isolation (enterprise, compliance)
- Hive Mode: Multiple tools sharing user context (personal AI, MCP)
Communication Patterns
One-to-one messaging:
const result = await cortex.a2a.send({
from: "finance-agent",
to: "hr-agent",
message: "What is the Q4 headcount budget?",
importance: 85,
userId: "user-123", // Optional: if about a user
contextId: "ctx-456", // Optional: link to workflow
metadata: {
tags: ["budget", "q4"],
priority: "urgent",
},
});
console.log(result.messageId); // "a2a-msg-123"
Send and wait for response:
const response = await cortex.a2a.request({
from: "finance-agent",
to: "hr-agent",
message: "What is the Q4 budget?",
timeout: 30000, // 30 seconds
retries: 2,
});
console.log(response.response); // "5 new hires approved"
console.log(response.responseTime); // 2453ms
One-to-many messaging:
const result = await cortex.a2a.broadcast({
from: "manager-agent",
to: ["finance-agent", "hr-agent", "ops-agent"],
message: "Sprint review Friday at 2 PM",
importance: 70,
});
console.log(`Sent to ${result.recipients.length} agents`);
Message Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
from | string | Yes | — | Sender agent/space ID |
to | string | string[] | Yes | — | Recipient agent/space ID(s) |
message | string | Yes | — | Message content |
importance | number | No | 60 | Importance (0-100) |
userId | string | No | — | User this message relates to |
contextId | string | No | — | Link to context chain workflow |
metadata | object | No | — | Custom data (tags, priority, etc.) |
replyTo | string | No | — | Message ID being replied to |
Reading Messages
The convenience methods getInbox(), getSent(), and markRead() are planned for a future release. See A2A Communication Enhancements for details. Use the workaround below for now.
// Get received messages (workaround using memory.search)
const inbox = await cortex.memory.search("hr-agent", "*", {
source: { type: "a2a", recipient: "hr-agent" },
limit: 20,
// Filter unread messages if needed (check metadata.unread or similar)
});
inbox.forEach(msg => {
console.log(`From: ${msg.metadata?.fromAgent || msg.source?.sender}`);
console.log(`Message: ${msg.content}`);
console.log(`Importance: ${msg.importance}`);
});
// Note: markRead() is planned - for now, you can track read status
// in your application or update message metadata if needed
The convenience method getSent() is planned for a future release. See A2A Communication Enhancements for details. Use the workaround below for now.
// Get sent messages (workaround using memory.search)
const sent = await cortex.memory.search("finance-agent", "*", {
source: { type: "a2a", sender: "finance-agent" },
limit: 20,
});
sent.forEach(msg => {
console.log(`To: ${msg.metadata?.toAgent || msg.source?.recipient}`);
console.log(`Message: ${msg.content}`);
});
// Get conversation between two agents
const convo = await cortex.a2a.getConversation(
"finance-agent",
"hr-agent",
{
since: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
minImportance: 50,
limit: 100,
}
);
convo.messages.forEach(msg => {
console.log(`[${msg.from}]: ${msg.message}`);
});
Common Patterns
Delegation with Context
// Create workflow context
const context = await cortex.contexts.create({
purpose: "Process refund for order #789",
memorySpaceId: "supervisor-space",
userId: "customer-abc",
});
// Delegate via A2A with context link
await cortex.a2a.send({
from: "supervisor-agent",
to: "specialist-agent",
message: "Please handle the customer refund",
importance: 85,
userId: "customer-abc",
contextId: context.id, // Links to workflow
});
// Specialist can access full context
const ctx = await cortex.contexts.get(context.id, { includeChain: true });
Team Announcements
await cortex.a2a.broadcast({
from: "manager-agent",
to: ["agent-1", "agent-2", "agent-3"],
message: "New policy: Refunds over $1000 require approval",
importance: 90,
metadata: {
tags: ["policy", "announcement"],
policyId: "POL-789",
},
});
Request-Response
// Responder handles incoming requests
async function handleRequests(agentId: string) {
// Workaround: use memory.search() instead of getInbox()
const requests = await cortex.memory.search(agentId, "*", {
source: { type: "a2a", recipient: agentId },
metadata: {
requiresResponse: true,
responded: false
},
});
for (const req of requests) {
const answer = await processRequest(req.content);
const fromAgent = req.metadata?.fromAgent || req.source?.sender;
await cortex.a2a.send({
from: agentId,
to: fromAgent,
message: answer,
replyTo: req.id,
});
}
}
Querying A2A Messages
Use standard memory search with source.type = 'a2a':
// All A2A for an agent
const allA2A = await cortex.memory.search("agent-1", "*", {
source: { type: "a2a" },
});
// Messages from specific agent
const fromFinance = await cortex.memory.search("hr-agent", "*", {
source: { type: "a2a" },
metadata: { fromAgent: "finance-agent" },
});
// High-importance A2A
const urgent = await cortex.memory.search("agent-1", "*", {
source: { type: "a2a" },
minImportance: 85,
});
// Semantic search across A2A
const budgetMsgs = await cortex.memory.search("agent-1", "budget approval", {
embedding: await embed("budget approval"),
source: { type: "a2a" },
});
Best Practices
const IMPORTANCE = {
CRITICAL: 95, // Major decisions, approvals
URGENT: 85, // Time-sensitive requests
IMPORTANT: 75, // Key information
STANDARD: 60, // Regular collaboration (default)
FYI: 40, // Nice-to-know
NOTIFICATION: 30 // Low-priority
};
await cortex.a2a.send({
from: "agent-1",
to: "agent-2",
message: "Approved budget increase",
metadata: {
tags: ["approval", "budget", "finance", "urgent"],
approvalType: "budget",
amount: 50000,
},
});
// Always link A2A to context when part of workflow
await cortex.a2a.send({
from: "supervisor",
to: "specialist",
message: "Handle this request",
contextId: context.id, // Enables full traceability
});
Architecture
A2A is a convenience layer on top of agent memory. Every A2A message is stored as a memory with source.type = 'a2a'. You can always query using the standard memory API.
What cortex.a2a.send() does:
- Creates/finds A2A conversation in ACID store
- Adds message to conversation (immutable)
- Stores in sender's vector memory (outbound)
- Stores in receiver's vector memory (inbound)
- Links both to ACID via
conversationRef
This is ~50 lines of code reduced to 7 lines with the helper.