A2A Protocol (Agent-to-Agent)
Native implementation of Google's A2A Protocol v0.3 for cross-framework agent interoperability.
Overview
The @cogitator-ai/a2a package implements Google's A2A Protocol v0.3 — the open standard for agent-to-agent communication. It enables Cogitator agents to interoperate with agents from any framework (LangChain, CrewAI, etc.) via a standardized JSON-RPC protocol.
- A2AServer -- expose any Cogitator agent as an A2A-compliant service with auto-generated Agent Cards
- A2AClient -- connect to any remote A2A agent, with discovery, messaging, and streaming
- asTool() Bridge -- wrap remote A2A agents as local Cogitator tools for seamless delegation
- Framework Adapters -- Express, Hono, Fastify, Koa, Next.js
pnpm add @cogitator-ai/a2aA2AServer
Exposing Agents
Create an A2A server to expose your agents to any A2A-compatible client:
import { Cogitator, Agent } from '@cogitator-ai/core';
import { A2AServer } from '@cogitator-ai/a2a';
import { a2aExpress } from '@cogitator-ai/a2a/express';
import express from 'express';
const cogitator = new Cogitator();
const researcher = new Agent({
name: 'researcher',
description: 'Research agent that finds information',
model: 'openai/gpt-4o',
instructions: 'You are a research assistant.',
});
const a2aServer = new A2AServer({
agents: { researcher },
cogitator,
cardUrl: 'https://my-server.com',
});
const app = express();
app.use(a2aExpress(a2aServer));
app.listen(3000);This exposes:
GET /.well-known/agent.json-- Agent Card discoveryPOST /a2a-- JSON-RPC endpoint for messaging, streaming, and task management
Agent Cards
Agent Cards are auto-generated from your Agent configuration. Each tool becomes a skill in the card:
const card = a2aServer.getAgentCard('researcher');{
"name": "researcher",
"description": "Research agent that finds information",
"url": "https://my-server.com",
"version": "0.3",
"capabilities": {
"streaming": true,
"pushNotifications": false
},
"skills": [
{
"id": "web_search",
"name": "web_search",
"description": "Search the web for information"
}
]
}Multiple Agents
Register multiple agents on a single server:
const a2aServer = new A2AServer({
agents: {
researcher: researchAgent,
writer: writerAgent,
analyst: analystAgent,
},
cogitator,
});A2AClient
Connecting to a Remote Agent
import { A2AClient } from '@cogitator-ai/a2a';
const client = new A2AClient('https://remote-agent.example.com');
const card = await client.agentCard();
console.log(`Agent: ${card.name}`);
console.log(`Skills: ${card.skills.map(s => s.name).join(', ')}`);Sending Messages
const task = await client.sendMessage({
role: 'user',
parts: [{ type: 'text', text: 'Research quantum computing trends' }],
});
console.log(task.status.state);
for (const artifact of task.artifacts) {
for (const part of artifact.parts) {
if (part.type === 'text') console.log(part.text);
}
}Streaming
for await (const event of client.sendMessageStream({
role: 'user',
parts: [{ type: 'text', text: 'Analyze market trends' }],
})) {
if (event.type === 'status-update') {
console.log(`Status: ${event.status.state}`);
} else if (event.type === 'artifact-update') {
console.log(`Artifact: ${event.artifact.id}`);
}
}Task Management
const task = await client.getTask('task_abc123');
await client.cancelTask('task_abc123');asTool() Bridge
The killer feature -- wrap any remote A2A agent as a local Cogitator tool:
const client = new A2AClient('https://research-agent.example.com');
const card = await client.agentCard();
const remoteTool = client.asToolFromCard(card, {
name: 'remote_researcher',
description: 'Delegate research to remote agent',
});
const orchestrator = new Agent({
name: 'orchestrator',
model: 'openai/gpt-4o',
instructions: 'Use the remote_researcher for information gathering.',
tools: [remoteTool],
});
const result = await cogitator.run(orchestrator, {
input: 'Write a report on AI trends',
});The orchestrator agent calls the remote researcher via A2A protocol transparently -- it looks like a regular tool call.
Multi-Turn Conversations
A2A supports multi-turn conversations where follow-up messages continue an existing task. Each task belongs to a contextId that groups related tasks into a conversation.
Client-Side
Use continueTask to send follow-up messages to an existing task:
const client = new A2AClient('https://remote-agent.example.com');
const task = await client.sendMessage({
role: 'user',
parts: [{ type: 'text', text: 'Research quantum computing' }],
});
const followUp = await client.continueTask(
task.id,
'Now compare it with classical computing'
);
console.log(task.contextId === followUp.contextId); // trueServer-Side
On the server, multi-turn is handled automatically. When a message includes a taskId, the server appends the new message to the existing task history and re-runs the agent with the full conversation context. No additional configuration needed.
Listing Tasks
Query stored tasks with filtering and pagination via the tasks/list method:
const client = new A2AClient('https://remote-agent.example.com');
const allTasks = await client.listTasks();
const conversationTasks = await client.listTasks({
contextId: 'ctx_abc123',
});
const completedTasks = await client.listTasks({
state: 'completed',
limit: 10,
offset: 0,
});Tasks are returned sorted by timestamp (newest first). The TaskFilter supports:
| Field | Type | Description |
|---|---|---|
contextId | string | Filter by conversation context |
state | TaskState | Filter by task state |
limit | number | Max number of tasks to return |
offset | number | Number of tasks to skip |
Token Streaming
SSE streaming now includes TokenStreamEvent alongside status and artifact updates, giving clients real-time character-by-character output:
for await (const event of client.sendMessageStream({
role: 'user',
parts: [{ type: 'text', text: 'Write a poem about the ocean' }],
})) {
switch (event.type) {
case 'token':
process.stdout.write(event.token);
break;
case 'status-update':
console.log(`\nStatus: ${event.status.state}`);
break;
case 'artifact-update':
console.log(`\nArtifact: ${event.artifact.id}`);
break;
}
}The A2AStreamEvent union type includes three event types:
| Event Type | Description |
|---|---|
status-update | Task state changed (working, completed) |
artifact-update | New artifact produced by the agent |
token | Individual token from LLM output |
The server emits token events via the onToken callback passed to Cogitator.run().
RedisTaskStore
For production deployments, use RedisTaskStore instead of the default in-memory store:
import { A2AServer, RedisTaskStore } from '@cogitator-ai/a2a';
import Redis from 'ioredis';
const redis = new Redis('redis://localhost:6379');
const a2aServer = new A2AServer({
agents: { researcher },
cogitator,
taskStore: new RedisTaskStore({
client: redis,
keyPrefix: 'a2a:task:',
ttl: 86400,
}),
});RedisTaskStore accepts any Redis client that implements the RedisClientLike interface (get, set, del, keys, and optionally setex). This works with ioredis, redis, and most Redis client libraries.
Configuration options:
| Option | Type | Default | Description |
|---|---|---|---|
client | RedisClientLike | required | Redis client instance |
keyPrefix | string | a2a:task: | Key prefix for task storage |
ttl | number | none | TTL in seconds (uses SETEX if set) |
Push Notifications
Register webhooks to receive task event notifications instead of polling or holding open SSE connections. When a task's status or artifacts change, the server POSTs the event to all registered webhook URLs.
Registering Webhooks
const client = new A2AClient('https://remote-agent.example.com');
const task = await client.sendMessage({
role: 'user',
parts: [{ type: 'text', text: 'Run a long analysis' }],
});
const config = await client.createPushNotification(task.id, {
webhookUrl: 'https://my-app.com/webhooks/a2a',
authenticationInfo: {
scheme: 'bearer',
credentials: { token: 'my-webhook-secret' },
},
});Managing Webhooks
const configs = await client.listPushNotifications(task.id);
const single = await client.getPushNotification(task.id, config.id!);
await client.deletePushNotification(task.id, config.id!);Server Configuration
Enable push notifications by providing a PushNotificationStore:
import { A2AServer, InMemoryPushNotificationStore } from '@cogitator-ai/a2a';
const a2aServer = new A2AServer({
agents: { researcher },
cogitator,
pushNotificationStore: new InMemoryPushNotificationStore(),
});The server automatically sends A2AStreamEvent payloads to registered webhooks on status and artifact updates. Authentication schemes supported: bearer, apiKey, basic, oauth2.
Agent Card Signing
Sign Agent Cards with HMAC-SHA256 to let clients verify card integrity:
Server-Side
Configure cardSigning on the server to automatically sign all Agent Cards:
const a2aServer = new A2AServer({
agents: { researcher },
cogitator,
cardSigning: {
algorithm: 'hmac-sha256',
secret: process.env.CARD_SIGNING_SECRET!,
},
});Client-Side Verification
const client = new A2AClient('https://remote-agent.example.com');
const isValid = await client.verifyAgentCard(process.env.CARD_SIGNING_SECRET!);Manual Signing and Verification
import { signAgentCard, verifyAgentCardSignature } from '@cogitator-ai/a2a';
const signed = signAgentCard(card, { secret: 'shared-secret' });
console.log(signed.signature); // "hmac-sha256:a1b2c3..."
const valid = verifyAgentCardSignature(signed, 'shared-secret');Extended Agent Card
The agent/extendedCard method provides an authenticated endpoint that returns additional agent details not included in the public Agent Card -- rate limits, pricing, extended skills, and custom metadata.
Server Configuration
const a2aServer = new A2AServer({
agents: { researcher },
cogitator,
extendedCardGenerator: (agentName) => ({
...a2aServer.getAgentCard(agentName),
extendedSkills: [
{
id: 'deep_research',
name: 'deep_research',
description: 'Multi-source deep research',
inputModes: ['text/plain'],
outputModes: ['text/plain', 'application/json'],
},
],
rateLimit: { requestsPerMinute: 60 },
pricing: { model: 'per-request', details: '$0.01 per task' },
metadata: { version: '2.1.0', region: 'us-east-1' },
}),
});Fetching from Client
const client = new A2AClient('https://remote-agent.example.com', {
headers: { Authorization: 'Bearer my-token' },
});
const extendedCard = await client.extendedAgentCard();
console.log(extendedCard.rateLimit);
console.log(extendedCard.pricing);
console.log(extendedCard.extendedSkills);The Agent Card advertises this capability via capabilities.extendedAgentCard: true.
Framework Adapters
Express
import { a2aExpress } from '@cogitator-ai/a2a/express';
app.use(a2aExpress(a2aServer));Hono
import { a2aHono } from '@cogitator-ai/a2a/hono';
app.route('/a2a', a2aHono(a2aServer));Fastify
import { a2aFastify } from '@cogitator-ai/a2a/fastify';
fastify.register(a2aFastify(a2aServer));Koa
import { a2aKoa } from '@cogitator-ai/a2a/koa';
app.use(a2aKoa(a2aServer));Next.js
import { a2aNext } from '@cogitator-ai/a2a/next';
export const { GET, POST } = a2aNext(a2aServer);Task Store
Tasks are stored in memory by default. For production, implement the TaskStore interface:
import { A2AServer, InMemoryTaskStore } from '@cogitator-ai/a2a';
const a2aServer = new A2AServer({
agents: { researcher },
cogitator,
taskStore: new InMemoryTaskStore(),
});Authentication
const a2aServer = new A2AServer({
agents: { researcher },
cogitator,
auth: {
type: 'bearer',
validate: async (token) => {
return token === process.env.A2A_SECRET;
},
},
});Clients include credentials via headers:
const client = new A2AClient('https://remote-agent.example.com', {
headers: {
Authorization: 'Bearer my-secret-token',
},
});A2A Protocol Methods
| Method | Description |
|---|---|
message/send | Send a message and get a completed task |
message/stream | Send a message with SSE streaming (incl. token events) |
tasks/get | Retrieve a task by ID |
tasks/cancel | Cancel a running task |
tasks/list | List tasks with filtering and pagination |
tasks/pushNotification/create | Register a webhook for task events |
tasks/pushNotification/get | Get a push notification config |
tasks/pushNotification/list | List push notification configs for a task |
tasks/pushNotification/delete | Remove a push notification config |
agent/extendedCard | Fetch extended Agent Card (authenticated) |
Task States
| State | Description |
|---|---|
working | Agent is processing |
completed | Task finished successfully |
failed | Task encountered an error |
input-required | Agent needs more input |
canceled | Task was canceled |
rejected | Server rejected the task |
Model Context Protocol (MCP)
Full MCP support -- connect to external MCP servers to use their tools, or expose Cogitator tools as an MCP server.
Reflection Engine
Enable agents to reflect on their own outputs, extract insights from past executions, and continuously improve through self-evaluation loops.