Cogitator
Integrations

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/a2a

A2AServer

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 discovery
  • POST /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); // true

Server-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:

FieldTypeDescription
contextIdstringFilter by conversation context
stateTaskStateFilter by task state
limitnumberMax number of tasks to return
offsetnumberNumber 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 TypeDescription
status-updateTask state changed (working, completed)
artifact-updateNew artifact produced by the agent
tokenIndividual 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:

OptionTypeDefaultDescription
clientRedisClientLikerequiredRedis client instance
keyPrefixstringa2a:task:Key prefix for task storage
ttlnumbernoneTTL 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

MethodDescription
message/sendSend a message and get a completed task
message/streamSend a message with SSE streaming (incl. token events)
tasks/getRetrieve a task by ID
tasks/cancelCancel a running task
tasks/listList tasks with filtering and pagination
tasks/pushNotification/createRegister a webhook for task events
tasks/pushNotification/getGet a push notification config
tasks/pushNotification/listList push notification configs for a task
tasks/pushNotification/deleteRemove a push notification config
agent/extendedCardFetch extended Agent Card (authenticated)

Task States

StateDescription
workingAgent is processing
completedTask finished successfully
failedTask encountered an error
input-requiredAgent needs more input
canceledTask was canceled
rejectedServer rejected the task

On this page