Skip to main content

A2A Communication API

Complete API reference for agent-to-agent communication helpers.

Overview​

The A2A Communication API (cortex.a2a.*) provides convenience helpers for inter-agent communication. It's not a separate storage system - it's syntactic sugar over the standard memory system with source.type='a2a'.

Infrastructure Requirement

Some A2A operations require real-time pub/sub infrastructure.

OperationPub/Sub RequiredWorks WithoutWhy
send()NoYesFire-and-forget (async)
request()YesNoNeeds real-time response notification
broadcast()Yes (optimal)DegradedCan store without pub/sub, but no delivery confirmation
subscribe()YesNoReal-time inbox notifications (Planned)
getConversation()NoYesDatabase query only

Pub/Sub Options:

  • Direct Mode: Bring your own Redis, RabbitMQ, NATS, or similar
  • Cloud Mode: Pub/sub infrastructure included and optimized
Key Insight

A2A = Agent Memory + Pub/Sub (optional) + Convenience

Validation Constraints:

FieldConstraintDefaultDetails
Agent IDs (from, to)Pattern: /^[a-zA-Z0-9_-]+$/—Only alphanumeric, hyphens, underscores
Agent IDs (from, to)Max length: 100 characters——
messageMax size: 100KB—Measured in UTF-8 bytes
importanceInteger 0-10060 for send/broadcast, 70 for request—
timeoutInteger 1000-300000ms30000ms (30 seconds)—
retriesInteger 0-101—
to[] (broadcast)Max 100 recipients—No duplicates, sender not allowed
limitInteger 1-1000100—
offsetInteger >= 00—
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Approach 1: A2A Helper (RECOMMENDED - 7 lines)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
await cortex.a2a.send({
from: "finance-agent",
to: "hr-agent",
message: "What is the Q4 budget?",
importance: 85,
});
// Done! Handles ACID + both Vector memories automatically

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Approach 2: Layer 3 remember() (Better than manual - 20 lines)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Get/create A2A conversation
const conversationId = await getOrCreateA2AConversation(
"finance-agent",
"hr-agent",
);

// Store in sender's memory (ACID + Vector)
await cortex.memory.remember({
memorySpaceId: "finance-agent-space",
conversationId,
userMessage: "What is the Q4 budget?",
agentResponse: "[Sent to hr-agent]",
userId: "finance-agent", // Sender as "user"
userName: "Finance Agent",
});

// Store in receiver's memory (ACID + Vector)
await cortex.memory.remember({
memorySpaceId: "hr-agent-space",
conversationId,
userMessage: "[From finance-agent]",
agentResponse: "What is the Q4 budget?",
userId: "finance-agent", // Sender as source
userName: "Finance Agent",
});
// Works, but awkward (remember() is for user-agent, not agent-agent)
// Still need to manage conversationId manually
// Have to call remember() twice (once per agent)

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Approach 3: Manual Layer 1 + Layer 2 (Most code - 50+ lines)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 1. Get or create A2A conversation in ACID (Layer 1a)
let conversationId;
const existing = await cortex.conversations.search({
type: "agent-agent",
participants: { $all: ["finance-agent", "hr-agent"] },
});

if (existing.length > 0) {
conversationId = existing[0].conversationId;
} else {
const conv = await cortex.conversations.create({
type: "agent-agent",
participants: { agent1: "finance-agent", agent2: "hr-agent" },
});
conversationId = conv.conversationId;
}

// 2. Add message to ACID conversation
const msg = await cortex.conversations.addMessage(conversationId, {
type: "a2a",
from: "finance-agent",
to: "hr-agent",
text: "What is the Q4 budget?",
timestamp: new Date(),
});

// 3. Store in sender's Vector memory (Layer 2)
await cortex.vector.store("finance-agent", {
content: "Sent to hr-agent: What is the Q4 budget?",
contentType: "raw",
embedding: await embed("What is the Q4 budget?"), // Optional
source: {
type: "a2a",
fromAgent: "finance-agent",
toAgent: "hr-agent",
timestamp: new Date(),
},
conversationRef: {
conversationId,
messageIds: [msg.id], // Links to ACID
},
metadata: {
importance: 85,
tags: ["a2a", "sent", "hr-agent"],
direction: "outbound",
},
});

// 4. Store in receiver's Vector memory (Layer 2)
await cortex.vector.store("hr-agent", {
content: "Received from finance-agent: What is the Q4 budget?",
contentType: "raw",
embedding: await embed("What is the Q4 budget?"),
source: {
type: "a2a",
fromAgent: "finance-agent",
toAgent: "hr-agent",
timestamp: new Date(),
},
conversationRef: {
conversationId,
messageIds: [msg.id], // Same ACID message
},
metadata: {
importance: 85,
tags: ["a2a", "received", "finance-agent"],
direction: "inbound",
},
});

// That's 50+ lines vs 7 lines with cortex.a2a.send()!

Code Reduction:

ApproachLines of CodeHandles ACIDBidirectional / Complexity
cortex.a2a.send()7AutomaticBoth agents / Low complexity
cortex.memory.remember()~20AutomaticOne agent (call 2x) / Medium complexity
Manual (Layer 1+2)50+ManualOne agent (call 2x) / High complexity
Why cortex.a2a.send() is best for A2A
  • Handles ACID conversation management
  • Stores in BOTH agents automatically
  • Consistent metadata and tagging
  • One call instead of four
  • 85% less code

Architecture Integration:

A2A Architecture
Layer 1a: ACID Conversations

A2A conversations (type='agent-agent') — Immutable threads

Layer 2: Vector Memories

Sender memory (direction='outbound') + Receiver memory (direction='inbound') — Both reference ACID via conversationRef

Pub/Sub Infrastructure

Direct Mode: Your Redis/RabbitMQ/NATS — Cloud Mode (planned): Cortex-managed infrastructure

A2A Helpers

Convenience wrappers (handle ACID + bidirectional Vector storage + pub/sub)

Why use A2A helpers
  • Reduce code - 7 lines instead of 40+
  • Bidirectional - Automatically stores in both agents
  • ACID tracking - Manages A2A conversations automatically
  • Real-time - Pub/sub notifications (BYO or Cloud-provided)
  • Consistent - Standard tagging and linking
  • Fewer bugs - Less code to maintain

Configuration​

Direct Mode: Developer Manages Execution + Pub/Sub​

What Cortex provides:

  • Storage APIs (database operations)
  • Metadata conventions (messageType, inReplyTo, etc.)
  • Patterns for request-response

What YOU must provide:

  • Agent execution infrastructure (how agents run)
  • Pub/sub infrastructure (your own Redis, RabbitMQ, etc.)
  • Agent subscription handlers (connecting agents to pub/sub)

Example Setup (Direct Mode):

import Redis from "ioredis"; // Standard ioredis package

// 1. Your Redis instance
const redis = new Redis(process.env.REDIS_URL);

// 2. Cortex SDK (storage only)
const cortex = new Cortex({
convexUrl: process.env.CONVEX_URL,
});

// 3. YOUR agent execution + subscription logic
async function runAgent(memorySpaceId: string) {
// Subscribe to memory space's inbox channel
redis.subscribe(`memoryspace:${memorySpaceId}:inbox`, (err) => {
if (err) throw err;
});

// Handle incoming notifications
redis.on("message", async (channel, message) => {
const notification = JSON.parse(message);

if (notification.type === "a2a-request") {
// 1. Fetch from Cortex storage
const request = await cortex.memory.get(
memorySpaceId,
notification.memoryId,
);

// 2. Process with your agent logic
const answer = await yourAgentLogic(request.content);

// 3. Store response in Cortex
await cortex.a2a.send({
from: memorySpaceId,
to: notification.from,
message: answer,
metadata: {
messageType: "response",
inReplyTo: notification.messageId,
},
});

// 4. Publish response notification (your pub/sub)
await redis.publish(
`agent:${notification.from}:responses:${notification.messageId}`,
JSON.stringify({ response: answer }),
);
}
});
}

// 4. Manual request() implementation (you build this)
async function manualRequest(
from: string,
to: string,
message: string,
timeout = 30000,
) {
// Send via Cortex (stores in database)
const sent = await cortex.a2a.send({
from,
to,
message,
metadata: {
messageType: "request",
requiresResponse: true,
},
});

// Publish notification (your pub/sub)
await redis.publish(
`agent:${to}:inbox`,
JSON.stringify({
type: "a2a-request",
messageId: sent.messageId,
memoryId: sent.receiverMemoryId,
from,
}),
);

// Wait for response (your pub/sub)
return new Promise((resolve, reject) => {
const channel = `agent:${from}:responses:${sent.messageId}`;

redis.subscribe(channel);
const handler = (ch, msg) => {
if (ch === channel) {
redis.unsubscribe(channel);
redis.off("message", handler);
resolve(JSON.parse(msg));
}
};
redis.on("message", handler);

setTimeout(() => {
redis.unsubscribe(channel);
redis.off("message", handler);
reject(new Error("Timeout"));
}, timeout);
});
}

// Start your agents
runAgent("hr-agent");
runAgent("finance-agent");

// Use your manual request
const response = await manualRequest(
"finance-agent",
"hr-agent",
"What is the budget?",
);

Key Points:

  • Cortex provides storage APIs (database operations)
  • YOU provide execution (agent runners) + pub/sub (Redis/RabbitMQ)
  • YOU wire them together (subscriptions, notifications)
  • Cortex just provides the patterns and storage conventions

Cloud Mode: Cortex Manages Everything​

Cloud Feature

Cloud Mode messaging layer provides managed pub/sub infrastructure (planned).

In Cloud Mode, Cortex will provide:

const cortex = new Cortex({
mode: "cloud",
apiKey: process.env.CORTEX_CLOUD_KEY,
});

// request() works automatically
const response = await cortex.a2a.request({
from: "finance-agent",
to: "hr-agent",
message: "What is the budget?",
});
// Cloud handles:
// - Pub/sub infrastructure (managed Redis)
// - Agent execution (webhooks or serverless functions)
// - Subscription wiring (automatic)

Core Helpers​

send()​

Send a message from one agent to another. Stores in ACID conversation + both agents' Vector memories.

No Pub/Sub Required

This is a fire-and-forget operation. Works in all modes without pub/sub.

Signature:

cortex.a2a.send(
params: A2ASendParams
): Promise<A2AMessage>

Parameters:

interface A2ASendParams {
// Required
from: string; // Sender agent ID
to: string; // Receiver agent ID
message: string; // The message content

// Optional linking
userId?: string; // If about a specific user (GDPR-enabled)
contextId?: string; // If part of a workflow

// Optional control
importance?: number; // 0-100 (default: 60)
trackConversation?: boolean; // Store in ACID (default: true)

// Cloud Mode
autoEmbed?: boolean; // Auto-generate embeddings (Cloud Mode)

// Metadata
metadata?: {
tags?: string[];
priority?: "low" | "normal" | "high" | "urgent";
[key: string]: any;
};
}

Returns:

interface A2AMessage {
messageId: string; // Unique message ID
sentAt: number; // Unix timestamp (milliseconds)

// ACID tracking
conversationId?: string; // ACID conversation (if trackConversation=true)
acidMessageId?: string; // Message ID in ACID

// Vector storage
senderMemoryId: string; // Memory ID in sender's storage
receiverMemoryId: string; // Memory ID in receiver's storage
}

Side Effects:

  • Creates/updates A2A conversation in Layer 1a (ACID) - unless trackConversation: false
  • Stores memory in sender's Vector index (Layer 2) with conversationRef
  • Stores memory in receiver's Vector index (Layer 2) with conversationRef
  • Both memories reference same ACID message

Example 1: Basic send

const result = await cortex.a2a.send({
from: "sales-agent",
to: "support-agent",
message: "Customer is asking about enterprise pricing",
importance: 70,
});

console.log(`Message ${result.messageId} sent`);
console.log(`ACID conversation: ${result.conversationId}`);
console.log(`Sender memory: ${result.senderMemoryId}`);
console.log(`Receiver memory: ${result.receiverMemoryId}`);

Example 2: With user context

// A2A about a specific user (GDPR-enabled)
await cortex.a2a.send({
from: "support-agent",
to: "billing-agent",
message: "User-123 requesting invoice for October",
userId: "user-123", // ← Links to user (enables GDPR cascade)
importance: 75,
metadata: {
tags: ["billing", "invoice", "request"],
requestType: "invoice",
},
});

// Later: GDPR deletion includes this A2A message
await cortex.users.delete("user-123", { cascade: true });
// A2A message deleted from both agents!

Example 3: With workflow context

// A2A as part of larger workflow
const context = await cortex.contexts.create({
purpose: "Process refund for order #789",
memorySpaceId: "supervisor-agent-space",
userId: "user-123",
});

await cortex.a2a.send({
from: "supervisor-agent",
to: "finance-agent",
message: "Please approve $500 refund",
userId: "user-123",
contextId: context.id, // ← Links to workflow
importance: 85,
});

// Finance agent can access full context
const ctx = await cortex.contexts.get(context.id);
console.log("Workflow purpose:", ctx.purpose);

Example 4: Fire-and-forget (no ACID tracking)

// Low-value notification (skip ACID to save storage)
await cortex.a2a.send({
from: "agent-1",
to: "agent-2",
message: "FYI: Daily report generated",
trackConversation: false, // ← Skip ACID storage
importance: 30,
});

// Still stores in Vector memories (both agents)
// But no conversationRef (not tracked in ACID)

Errors:

  • A2AValidationError('INVALID_AGENT_ID') - from or to is empty, malformed, or exceeds 100 chars
  • A2AValidationError('EMPTY_MESSAGE') - Message is empty or whitespace-only
  • A2AValidationError('MESSAGE_TOO_LARGE') - Message exceeds 100KB (UTF-8 bytes)
  • A2AValidationError('SAME_AGENT_COMMUNICATION') - from and to are the same agent
  • A2AValidationError('INVALID_IMPORTANCE') - Importance not integer 0-100

See Also:


request()​

Send a request and wait for response (synchronous request-response pattern).

Requires Pub/Sub Infrastructure

This operation requires real-time messaging.

  • Direct Mode: Configure your own Redis/RabbitMQ/NATS adapter (see Configuration)
  • Cloud Mode (planned): Pub/sub infrastructure will be included automatically

How it works:

  1. Agent A calls request() → Stores in database + publishes to Agent B's inbox channel
  2. Agent B receives pub/sub notification → Fetches from database → Processes → Responds
  3. Agent B sends response → Stores in database + publishes to Agent A's response channel
  4. Agent A receives pub/sub notification → Returns response to caller

Storage: Convex (persistent)
Notifications: Pub/sub (ephemeral, real-time)

Signature:

cortex.a2a.request(
params: A2ARequestParams
): Promise<A2AResponse>

Parameters:

interface A2ARequestParams {
// Required
from: string;
to: string;
message: string;

// Timeout control
timeout?: number; // ms (default: 30000)
retries?: number; // Retry attempts (default: 1)

// Optional linking
userId?: string;
contextId?: string;

// Optional
importance?: number; // 0-100
}

Returns:

interface A2AResponse {
response: string; // Response message
messageId: string; // Original request message ID
responseMessageId: string; // Response message ID
respondedAt: number; // Unix timestamp (milliseconds)
responseTime: number; // ms
}

Side Effects:

  • Sends request via send() (creates ACID + Vector memories)
  • Publishes notification to receiver's inbox channel (pub/sub)
  • Subscribes to response channel (pub/sub)
  • Marks request as responded when answer received
  • Throws timeout error if no response within timeout period

Example 1: Basic request

try {
const response = await cortex.a2a.request({
from: "finance-agent",
to: "hr-agent",
message: "What is the Q4 headcount budget?",
timeout: 30000, // 30 seconds
importance: 85,
});

console.log(`Response: ${response.response}`);
console.log(`Response time: ${response.responseTime}ms`);
} catch (error) {
if (error instanceof A2ATimeoutError) {
console.log("HR agent did not respond within 30 seconds");
// Fallback logic
}
}

Example 2: With retries

// Auto-retry on timeout
const response = await cortex.a2a.request({
from: "agent-1",
to: "agent-2",
message: "Complex calculation needed",
timeout: 60000, // 60 second timeout
retries: 3, // Will retry up to 3 times
importance: 85,
});

// Total possible wait: 60s × 3 = 180s max

Example 3: Receiver handles requests (you implement this)

// Direct Mode: YOU implement agent execution + pub/sub handling
// See Configuration section for complete example

// In Cloud Mode: This would be automatic via webhooks

Alternative: Batch processing without pub/sub

// If you need to manually check for requests (batch processing)
async function handleIncomingRequests(memorySpaceId: string, agentId: string) {
// List all A2A memories and filter for pending requests
const allA2A = await cortex.vector.list({
memorySpaceId,
sourceType: "a2a",
limit: 100,
});

// Filter for pending requests (client-side filtering)
const requests = allA2A.filter((m) => {
const meta = m.metadata as {
messageType?: string;
requiresResponse?: boolean;
responded?: boolean;
};
return (
meta.messageType === "request" &&
meta.requiresResponse === true &&
meta.responded !== true
);
});

for (const request of requests) {
const answer = await processRequest(request.content);
const meta = request.metadata as { fromAgent?: string; messageId?: string };

await cortex.a2a.send({
from: agentId,
to: meta.fromAgent!,
message: answer,
metadata: {
messageType: "response",
inReplyTo: meta.messageId,
},
});

// Mark as responded
await cortex.vector.update(memorySpaceId, request.memoryId, {
metadata: {
...request.metadata,
responded: true,
respondedAt: Date.now(),
},
});
}
}

// Run on schedule
cron.schedule("*/5 * * * *", () =>
handleIncomingRequests("hr-agent", "hr-agent"),
);

Errors:

  • A2AValidationError('INVALID_AGENT_ID') - from or to is empty, malformed, or exceeds 100 chars
  • A2AValidationError('EMPTY_MESSAGE') - Message is empty or whitespace-only
  • A2AValidationError('MESSAGE_TOO_LARGE') - Message exceeds 100KB (UTF-8 bytes)
  • A2AValidationError('SAME_AGENT_COMMUNICATION') - from and to are the same agent
  • A2AValidationError('INVALID_TIMEOUT') - Timeout not between 1000ms and 300000ms
  • A2AValidationError('INVALID_RETRIES') - Retries not integer 0-10
  • A2AValidationError('INVALID_IMPORTANCE') - Importance not integer 0-100
  • A2ATimeoutError - No response within timeout (includes pub/sub not configured)

See Also:


broadcast()​

Send one message to multiple agents efficiently.

Requires Pub/Sub Infrastructure

Optimized delivery requires real-time messaging.

  • Direct Mode: Configure your own pub/sub adapter
  • Cloud Mode (planned): Pub/sub infrastructure will be included

Signature:

cortex.a2a.broadcast(
params: A2ABroadcastParams
): Promise<A2ABroadcastResult>

Parameters:

interface A2ABroadcastParams {
// Required
from: string;
to: string[]; // Multiple recipients
message: string;

// Optional linking
userId?: string;
contextId?: string;

// Optional
importance?: number; // 0-100
trackConversation?: boolean; // Default: true

// Metadata
metadata?: {
tags?: string[];
[key: string]: any;
};
}

Returns:

interface A2ABroadcastResult {
messageId: string; // Broadcast ID (same for all)
sentAt: number; // Unix timestamp (milliseconds)
recipients: string[];

// Storage results
senderMemoryIds: string[]; // One per recipient
receiverMemoryIds: string[]; // One per recipient
memoriesCreated: number; // Total (sender + receiver for each)

// ACID tracking (if trackConversation=true)
conversationIds?: string[]; // One per recipient
}

Side Effects:

  • Creates N memories in sender's storage (one per recipient)
  • Creates N memories in receivers' storage (one per sender)
  • Total: 2N memories created
  • Each pair gets separate A2A conversation in ACID (if tracking enabled)

Example 1: Team announcement

const result = await cortex.a2a.broadcast({
from: "manager-agent",
to: ["dev-agent-1", "dev-agent-2", "qa-agent", "designer-agent"],
message: "Sprint review meeting Friday at 2 PM",
importance: 70,
metadata: {
tags: ["meeting", "sprint-review", "team"],
meetingId: "meeting-456",
},
});

console.log(`Broadcast to ${result.recipients.length} agents`);
console.log(`Created ${result.memoriesCreated} total memories`);
// 4 recipients = 8 memories (4 sender + 4 receiver)

Example 2: Policy update broadcast

// Notify all agents about policy change
const allAgents = await cortex.agents.list();

await cortex.a2a.broadcast({
from: "admin-agent",
to: allAgents.map((a) => a.id),
message: "New policy: All refunds over $1000 require manager approval",
importance: 90,
metadata: {
tags: ["policy", "announcement", "important"],
policyId: "POL-789",
effectiveDate: new Date("2025-11-01"),
},
});

// Each agent can query their announcements
const allA2A = await cortex.vector.list({
memorySpaceId: "agent-1",
sourceType: "a2a",
limit: 100,
});

// Filter for broadcast announcements
const announcements = allA2A.filter((m) => {
const meta = m.metadata as { broadcast?: boolean };
return meta.broadcast === true && m.tags.includes("announcement");
});

Errors:

  • A2AValidationError('INVALID_AGENT_ID') - from or any to[] entry is empty, malformed, or exceeds 100 chars
  • A2AValidationError('EMPTY_MESSAGE') - Message is empty or whitespace-only
  • A2AValidationError('MESSAGE_TOO_LARGE') - Message exceeds 100KB (UTF-8 bytes)
  • A2AValidationError('EMPTY_RECIPIENTS') - to[] array is empty
  • A2AValidationError('TOO_MANY_RECIPIENTS') - More than 100 recipients
  • A2AValidationError('DUPLICATE_RECIPIENTS') - Same agent ID appears multiple times
  • A2AValidationError('INVALID_RECIPIENT') - Sender is in recipients list
  • A2AValidationError('INVALID_IMPORTANCE') - Importance not integer 0-100

See Also:


getConversation()​

Get chronological conversation between two agents with rich filtering.

Signature:

cortex.a2a.getConversation(
agent1: string,
agent2: string,
filters?: A2AConversationFilters
): Promise<A2AConversation>

Parameters:

interface A2AConversationFilters {
// Time range
since?: Date;
until?: Date;

// Filtering
minImportance?: number; // 0-100
tags?: string[];
userId?: string; // A2A about specific user

// Pagination
limit?: number; // Default: 100, max: 1000
offset?: number;

// Format (currently only "chronological" is supported)
format?: "chronological"; // Default and only option
}

Returns:

interface A2AConversation {
participants: [string, string];
conversationId?: string; // ACID conversation ID (if exists)
messageCount: number;

messages: A2AConversationMessage[];

period: {
start: number; // Unix timestamp (milliseconds)
end: number; // Unix timestamp (milliseconds)
};

tags?: string[]; // Filtered tags
canRetrieveFullHistory: boolean; // True if ACID conversation exists
}

interface A2AConversationMessage {
from: string;
to: string;
message: string;
importance: number;
timestamp: number; // Unix timestamp (milliseconds)
messageId: string;
memoryId: string; // Vector memory ID
acidMessageId?: string; // ACID message ID (if tracked)
tags?: string[];
direction?: string; // 'outbound' or 'inbound'
broadcast?: boolean; // True if part of a broadcast
broadcastId?: string; // Broadcast ID (if broadcast)
}

Side Effects:

  • None (read-only)

Example 1: Get complete conversation

// Get all communication between two agents
const convo = await cortex.a2a.getConversation("finance-agent", "hr-agent");

console.log(`${convo.messageCount} messages exchanged`);
convo.messages.forEach((msg) => {
console.log(`[${msg.timestamp}] ${msg.from} → ${msg.to}`);
console.log(` ${msg.message}`);
});

// Can retrieve full ACID history if needed
if (convo.canRetrieveFullHistory) {
const fullHistory = await cortex.conversations.get(convo.conversationId);
console.log(
"Complete conversation:",
fullHistory.messages.length,
"messages",
);
}

Example 2: Filtered conversation

// Get only important budget-related messages from last 30 days
const filtered = await cortex.a2a.getConversation("finance-agent", "hr-agent", {
since: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
minImportance: 70,
tags: ["budget"],
limit: 50,
});

console.log(`Found ${filtered.messageCount} important budget discussions`);

Example 3: User-specific A2A

// Get all A2A about a specific user
const userRelated = await cortex.a2a.getConversation(
"support-agent",
"billing-agent",
{
userId: "user-123", // Only A2A about this user
since: new Date("2025-10-01"),
},
);

console.log(`${userRelated.messageCount} messages about user-123`);

Errors:

  • A2AValidationError('INVALID_AGENT_ID') - agent1 or agent2 is empty, malformed, or exceeds 100 chars
  • A2AValidationError('INVALID_DATE_RANGE') - 'since' is after 'until'
  • A2AValidationError('INVALID_IMPORTANCE') - minImportance not between 0-100
  • A2AValidationError('INVALID_LIMIT') - Limit not integer 1-1000
  • A2AValidationError('INVALID_OFFSET') - Offset is negative

See Also:


Querying A2A Messages (Using Memory API)​

Since A2A uses the memory system, you can use any memory operation on A2A messages:

Finding A2A Messages​

// All A2A for an agent (semantic search on "a2a" query)
const allA2A = await cortex.memory.search("agent-1", "a2a communication", {
sourceType: "a2a",
limit: 100,
});

// Use list() for non-semantic filtering by source type
const allA2AList = await cortex.vector.list({
memorySpaceId: "agent-1",
sourceType: "a2a",
limit: 100,
});

// Filter by tags (sent vs received are tagged)
const sent = await cortex.vector.list({
memorySpaceId: "agent-1",
sourceType: "a2a",
// Post-filter for 'sent' tag
});

Semantic Search on A2A​

// Semantic search across A2A messages
const results = await cortex.memory.search(
"agent-1",
"did we discuss the budget increase?",
{
embedding: await embed("budget increase discussion"),
sourceType: "a2a",
minImportance: 50,
limit: 10,
},
);

results.forEach((m) => {
const meta = m.metadata as { toAgent?: string; fromAgent?: string };
console.log(`${m.content}`);
console.log(` With: ${meta.toAgent || meta.fromAgent}`);
});

Finding Unanswered Requests​

// Get A2A requests and filter for pending
const a2aMemories = await cortex.vector.list({
memorySpaceId: "hr-agent",
sourceType: "a2a",
limit: 100,
});

// Filter for pending requests (client-side filtering)
const pending = a2aMemories.filter((m) => {
const meta = m.metadata as {
messageType?: string;
requiresResponse?: boolean;
responded?: boolean;
};
return (
meta.messageType === "request" &&
meta.requiresResponse === true &&
meta.responded !== true
);
});

console.log(`${pending.length} pending requests`);

Counting A2A Messages​

// Total A2A messages
const total = await cortex.vector.count({
memorySpaceId: "agent-1",
sourceType: "a2a",
});

console.log(`Total A2A messages: ${total}`);

Integration with Other Systems​

A2A + Contexts (Workflow Coordination)​

// Create workflow context
const context = await cortex.contexts.create({
purpose: "Process refund request",
memorySpaceId: "supervisor-agent-space",
userId: "user-123",
});

// Delegate via A2A linked to context
await cortex.a2a.send({
from: "supervisor-agent",
to: "specialist-agent",
message: "Please handle refund for ticket #456",
userId: "user-123",
contextId: context.id, // ← Links to workflow
importance: 85,
});

// Specialist can access context
const ctx = await cortex.contexts.get(context.id);

// Find all A2A for this workflow
const allA2A = await cortex.vector.list({
memorySpaceId: "supervisor-agent",
sourceType: "a2a",
limit: 100,
});

// Filter for this context
const workflowComms = allA2A.filter((m) => {
const meta = m.metadata as { contextId?: string };
return meta.contextId === context.id;
});

A2A + Conversations (User-Triggered)​

// User makes request
const userMsg = await cortex.conversations.addMessage("conv-456", {
role: "user",
text: "I need help with my refund",
userId: "user-123",
});

// Create context linked to conversation
const context = await cortex.contexts.create({
purpose: "Handle user refund request",
memorySpaceId: "support-agent-space",
userId: "user-123",
conversationRef: {
conversationId: "conv-456",
messageIds: [userMsg.id],
},
});

// A2A delegation with full traceability
await cortex.a2a.send({
from: "support-agent",
to: "finance-agent",
message: "User needs refund approval for $500",
userId: "user-123",
contextId: context.id,
});

// Finance agent can trace back to original user request
const ctx = await cortex.contexts.get(context.id, {
includeConversation: true,
});
console.log("Original user request:", ctx.triggerMessages[0].text);

A2A + Users (GDPR Cascade)​

// A2A about a user
await cortex.a2a.send({
from: "support-agent",
to: "billing-agent",
message: "User requesting account deletion",
userId: "user-123", // ← GDPR link
importance: 95,
});

// User requests deletion
const result = await cortex.users.delete("user-123", { cascade: true });

console.log(`A2A messages deleted: ${result.vectorMemoriesDeleted}`);
// Includes the A2A message above!

Advanced Patterns​

Pattern 1: Complete Traceability​

// Full traceability: User → Context → A2A → Memories

// 1. User conversation (Layer 1a - ACID)
const userMsg = await cortex.conversations.addMessage("conv-456", {
role: "user",
text: "I need a refund",
userId: "user-123",
});

// 2. Workflow context (linked to conversation)
const context = await cortex.contexts.create({
purpose: "Process refund",
memorySpaceId: "supervisor-agent-space",
userId: "user-123",
conversationRef: {
conversationId: "conv-456",
messageIds: [userMsg.id],
},
});

// 3. A2A delegation (linked to context AND conversation)
const a2aResult = await cortex.a2a.send({
from: "supervisor-agent",
to: "specialist-agent",
message: "Handle refund for user",
userId: "user-123",
contextId: context.id,
});

// Now specialist agent can access:
// - A2A message (in their memory)
const a2aMemory = await cortex.memory.get(
"specialist-agent",
a2aResult.receiverMemoryId,
);

// - Context chain
const ctx = await cortex.contexts.get(a2aMemory.metadata.contextId);

// - Original user conversation (via context)
const userConvo = await cortex.conversations.get(
ctx.conversationRef.conversationId,
);

// Complete audit trail!

Pattern 2: A2A Communication Analytics​

// Analyze communication patterns
async function analyzeA2ACommunication(
memorySpaceId: string,
period: number = 30,
) {
const sinceTimestamp = Date.now() - period * 24 * 60 * 60 * 1000;

// Get all A2A memories
const allMemories = await cortex.vector.list({
memorySpaceId,
sourceType: "a2a",
limit: 1000,
});

// Filter for period
const allA2A = allMemories.filter((m) => m.createdAt >= sinceTimestamp);

// Type metadata
type A2AMeta = {
direction?: string;
toAgent?: string;
fromAgent?: string;
};

// Sent vs received
const sent = allA2A.filter(
(m) => (m.metadata as A2AMeta).direction === "outbound",
);
const received = allA2A.filter(
(m) => (m.metadata as A2AMeta).direction === "inbound",
);

// By partner agent
const partners: Record<string, number> = {};
allA2A.forEach((m) => {
const meta = m.metadata as A2AMeta;
const partner = meta.toAgent || meta.fromAgent;
if (partner) {
partners[partner] = (partners[partner] || 0) + 1;
}
});

// By importance (importance is a top-level field on MemoryEntry)
const critical = allA2A.filter((m) => m.importance >= 90).length;
const important = allA2A.filter((m) => m.importance >= 70).length;

return {
total: allA2A.length,
sent: sent.length,
received: received.length,
partners,
avgImportance:
allA2A.reduce((sum, m) => sum + m.importance, 0) / allA2A.length || 0,
critical,
important,
};
}

const stats = await analyzeA2ACommunication("finance-agent", 30);
console.log("Last 30 days:", stats);

Pattern 3: Bulk Operations on A2A​

// List A2A memories for bulk processing
const a2aMemories = await cortex.vector.list({
memorySpaceId: "agent-1",
sourceType: "a2a",
limit: 1000,
});

// Filter old, low-importance messages for cleanup
const oldLowImportance = a2aMemories.filter((m) => {
const ninetyDaysAgo = Date.now() - 90 * 24 * 60 * 60 * 1000;
return m.createdAt < ninetyDaysAgo && m.importance <= 50;
});

// Update each memory individually (mark as archived)
for (const memory of oldLowImportance) {
await cortex.vector.update("agent-1", memory.memoryId, {
metadata: { ...memory.metadata, archived: true },
});
}

// Delete trivial old A2A one by one
const trivialOld = a2aMemories.filter((m) => {
const sixMonthsAgo = Date.now() - 180 * 24 * 60 * 60 * 1000;
return m.createdAt < sixMonthsAgo && m.importance <= 30 && m.accessCount <= 1;
});

for (const memory of trivialOld) {
await cortex.vector.delete("agent-1", memory.memoryId);
}

// Export A2A for audit (manual serialization)
const auditMemories = a2aMemories.filter((m) => {
const start = new Date("2025-10-01").getTime();
const end = new Date("2025-10-31").getTime();
return m.createdAt >= start && m.createdAt <= end;
});

// Write to file or process as needed
const auditData = JSON.stringify(auditMemories, null, 2);
// fs.writeFileSync('audits/october-a2a.json', auditData);

A2A Message Structure​

When you use A2A helpers, messages are stored as standard Vector memories with specific structure:

// Sender's memory
interface A2ASenderMemory extends MemoryEntry {
memorySpaceId: string; // Sender's memory space
content: string; // 'Sent to {recipient}: {message}'

source: {
type: "a2a";
fromAgent: string; // Sender
toAgent: string; // Recipient
timestamp: Date;
};

conversationRef?: {
// Links to ACID A2A conversation
conversationId: string; // e.g., 'a2a-conv-789'
messageIds: string[];
};

metadata: {
importance: number; // 0-100
tags: string[]; // Includes 'a2a', 'sent', recipient
direction: "outbound";
messageId: string; // A2A message ID

// Optional
contextId?: string; // Workflow link
userId?: string; // User link (GDPR)
broadcast?: boolean; // If broadcast
broadcastId?: string;
messageType?: "request" | "response";
inReplyTo?: string; // For responses
requiresResponse?: boolean;
responded?: boolean;
};
}

// Receiver's memory (similar structure with direction='inbound')

This means all memory operations work on A2A:

  • cortex.memory.search() - Semantic or text search
  • cortex.memory.get() - Retrieve by ID
  • cortex.memory.update() - Update (creates version)
  • cortex.memory.delete() - Delete
  • cortex.memory.count() - Count
  • All universal filters apply!

When to Use Each Approach​

Use A2A Helpers When:​

  • Simple send - One agent to another
  • Request-response - Need synchronous answer
  • Broadcasting - One to many
  • Viewing conversation - Chronological thread between two agents
  • Less code - Want convenience

Use Memory API Directly When:​

  • Complex queries - Semantic search, multiple filters
  • Bulk operations - updateMany, deleteMany on A2A messages
  • Custom logic - Need full control
  • Advanced filtering - Combining many criteria
  • Performance - Optimizing specific queries

Mix Both:​

// Send with helper (convenience)
await cortex.a2a.send({
from: "agent-1",
to: "agent-2",
message: "Important update",
importance: 85,
});

// Query with memory API (power) - semantic search
const results = await cortex.memory.search("agent-2", "important update", {
embedding: await embed("important update"),
sourceType: "a2a",
minImportance: 80,
limit: 20,
});

// Filter for last 7 days (client-side)
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
const important = results.filter((m) => m.createdAt >= sevenDaysAgo);

Best Practices​

1. Set Appropriate Importance​

// A2A importance guidelines (0-100 scale)
const A2A_IMPORTANCE = {
CRITICAL_DECISION: 95, // Major decisions, approvals
URGENT_REQUEST: 85, // Time-sensitive requests
IMPORTANT_INFO: 75, // Key information sharing
STANDARD_COLLAB: 60, // Regular collaboration (default)
STATUS_UPDATE: 50, // Routine updates
FYI: 40, // Nice-to-know information
NOTIFICATION: 30, // Low-priority notifications
DEBUG: 10, // Debug/diagnostic messages
};

await cortex.a2a.send({
from: "agent-1",
to: "agent-2",
message: "Board approved budget increase",
importance: A2A_IMPORTANCE.CRITICAL_DECISION,
});

2. Use Tags for Organization​

await cortex.a2a.send({
from: "agent-1",
to: "agent-2",
message: "Approved budget increase",
importance: 90,
metadata: {
tags: [
"a2a", // Automatic
"approval", // Action type
"budget", // Topic
"finance", // Department
"urgent", // Priority
],
approvalType: "budget",
approvedAmount: 50000,
},
});

// Query by tags using semantic search
const approvals = await cortex.memory.search("agent-1", "budget approval", {
sourceType: "a2a",
tags: ["approval", "budget"],
limit: 20,
});
// Always link A2A to context when part of workflow
const context = await cortex.contexts.create({
purpose: "Process refund",
memorySpaceId: "supervisor-agent-space",
});

await cortex.a2a.send({
from: "supervisor-agent",
to: "specialist-agent",
message: "Handle this refund",
contextId: context.id, // ← Links to workflow
});

4. Track Conversation for Audit​

// Default: Track in ACID (audit trail)
await cortex.a2a.send({
from: "agent-1",
to: "agent-2",
message: "Important decision",
importance: 90,
// trackConversation: true (default)
});

// Only skip for ephemeral, low-value messages
await cortex.a2a.send({
from: "agent-1",
to: "agent-2",
message: "Heartbeat ping",
importance: 10,
trackConversation: false, // Skip ACID (save storage)
});

5. Handle Request Timeouts​

// Use try-catch for request() pattern
async function askWithFallback(from: string, to: string, message: string) {
try {
const response = await cortex.a2a.request({
from,
to,
message,
timeout: 30000,
retries: 2,
});

return response.response;
} catch (error) {
if (error instanceof A2ATimeoutError) {
// Fallback to async
await cortex.a2a.send({
from,
to,
message: `${message} (respond when ready)`,
metadata: { async: true },
});

return null; // Will check later
}
throw error;
}
}

Comparison: Helper vs Direct​

OperationA2A HelperMemory API DirectLines of Code
Send messagea2a.send()ACID + 2x Vector stores7 vs 40+
Request-responsea2a.request()Send + polling loop5 vs 35+
Broadcasta2a.broadcast()Loop + Nx2 stores8 vs 50+
Get conversationa2a.getConversation()Bidirectional search + merge6 vs 40+
Total saved26 lines165+ lines85% reduction
Helpers are pure convenience

Everything A2A helpers do can be done with the memory API directly. But they eliminate boilerplate and ensure consistency.


Cloud Mode Features (Planned)​

Cloud Mode Under Development

The following features are planned for Cortex Cloud Mode. The SDK provides full A2A functionality today via Direct Mode.

A2A Analytics Dashboard​

  • Communication frequency between agent pairs
  • Average response times
  • Bottleneck identification (slow responders)
  • Collaboration graphs and network visualization
  • Topic clustering from A2A messages

Smart Routing​

  • AI suggests which agent to ask based on expertise
  • Learns from communication patterns
  • Automatic load balancing

Automated Summarization​

  • Daily/weekly A2A summaries per agent
  • Topic extraction and clustering
  • Action item detection

Graph-Lite Capabilities​

A2A communication creates an agent collaboration graph:

Graph Structure:

  • Nodes: Agents
  • Edges: Messages (with properties: direction, importance, timestamp)

A2A as Directed Graph:

finance-agent
├──[SENT_TO {importance: 85}]──> hr-agent
├──[SENT_TO {importance: 90}]──> legal-agent
├──[RECEIVED_FROM]<──────────── ceo-agent
└──[SENT_TO]────────────────────> ops-agent

Graph Queries via Memory API:

type A2AMeta = {
direction?: string;
toAgent?: string;
fromAgent?: string;
};

// 1-hop: Direct collaborators
const allA2A = await cortex.vector.list({
memorySpaceId: "finance-agent",
sourceType: "a2a",
limit: 1000,
});

const sent = allA2A.filter(
(m) => (m.metadata as A2AMeta).direction === "outbound",
);
const directCollaborators = new Set(
sent.map((m) => (m.metadata as A2AMeta).toAgent).filter(Boolean),
);

// 2-hop: Collaborator's collaborators
const secondDegree = new Set<string>();
for (const agent of directCollaborators) {
const theirA2A = await cortex.vector.list({
memorySpaceId: agent as string,
sourceType: "a2a",
limit: 1000,
});

const theirSent = theirA2A.filter(
(m) => (m.metadata as A2AMeta).direction === "outbound",
);

theirSent.forEach((m) => {
const toAgent = (m.metadata as A2AMeta).toAgent;
if (toAgent && toAgent !== "finance-agent") {
secondDegree.add(toAgent);
}
});
}

// Build weighted graph (edge weights = message count + importance)
const edges = new Map<string, { count: number; avgImportance: number }>();
sent.forEach((m) => {
const partner = (m.metadata as A2AMeta).toAgent;
if (!partner) return;

const current = edges.get(partner) || { count: 0, avgImportance: 0 };
edges.set(partner, {
count: current.count + 1,
avgImportance:
(current.avgImportance * current.count + m.importance) /
(current.count + 1),
});
});

Performance:

  • 1-hop (direct collaborators): 20-50ms
  • 2-hop (network expansion): 100-200ms
  • 3-hop: 200-400ms (consider graph DB for frequent deep queries)

Network Analysis:

For advanced graph analytics (community detection, centrality, influence metrics), consider integrating a graph database:

Learn more: Graph Database Integration


Error Reference​

A2AValidationError Class​

The SDK exports a custom error class for validation failures:

import { A2AValidationError } from "@cortex/sdk/a2a";

// Error properties
interface A2AValidationError extends Error {
name: "A2AValidationError";
code: string; // Error code (e.g., 'INVALID_AGENT_ID')
field?: string; // Field that failed validation (e.g., 'from', 'to')
}

// Usage
try {
await cortex.a2a.send({
from: "", // Invalid - empty
to: "agent-2",
message: "Hello",
});
} catch (error) {
if (error instanceof A2AValidationError) {
console.log(`Validation failed: ${error.code}`);
console.log(`Field: ${error.field}`); // 'from'
}
}

A2ATimeoutError Class​

Thrown when request() times out waiting for a response:

import { A2ATimeoutError } from "@cortex/sdk";

interface A2ATimeoutError extends Error {
name: "A2ATimeoutError";
messageId: string; // ID of the request message
timeout: number; // Timeout value in milliseconds
}

try {
await cortex.a2a.request({
from: "agent-1",
to: "agent-2",
message: "Hello",
});
} catch (error) {
if (error instanceof A2ATimeoutError) {
console.log(
`Request ${error.messageId} timed out after ${error.timeout}ms`,
);
}
}

All Error Codes​

Error CodeDescriptionCause
INVALID_AGENT_IDAgent ID is invalidEmpty, malformed, or exceeds 100 chars
EMPTY_MESSAGEMessage is emptyMessage is empty or whitespace-only
MESSAGE_TOO_LARGEMessage exceeds limitMessage exceeds 100KB (UTF-8 bytes)
EMPTY_RECIPIENTSNo recipientsto[] array is empty
TOO_MANY_RECIPIENTSToo many recipientsMore than 100 recipients in broadcast
DUPLICATE_RECIPIENTSDuplicate recipientsSame agent ID appears multiple times in to[]
INVALID_RECIPIENTInvalid recipientSender included in recipients list
SAME_AGENT_COMMUNICATIONSelf-communicationfrom and to are the same agent
INVALID_IMPORTANCEInvalid importanceImportance not integer 0-100
INVALID_TIMEOUTInvalid timeoutTimeout not between 1000ms and 300000ms
INVALID_RETRIESInvalid retriesRetries not integer 0-10
INVALID_DATE_RANGEInvalid date range'since' is after 'until'
INVALID_LIMITInvalid limitLimit not integer 1-1000
INVALID_OFFSETInvalid offsetOffset is negative
PUBSUB_NOT_CONFIGUREDPub/sub requiredrequest() needs pub/sub adapter
A2ATimeoutErrorRequest timeoutNo response within timeout
A2AValidationErrorValidation failedClient-side validation error (see code/field)
CONVEX_ERRORDatabase errorConvex operation failed

See Also:


Next Steps​


Questions? Ask in GitHub Discussions.