Skip to main content
A2A (Agent-to-Agent) is Google’s open protocol for agent interoperability. It lets agents built on different frameworks - from different vendors - discover and communicate with each other over a standard JSON-RPC HTTP interface. Any A2A-compatible client can find your agent via its AgentCard and invoke it without knowing which framework you used. A2AAdapter wraps any Vibes agent and serves it as a fully compliant A2A server.

Architecture

Setup

Install @vibesjs/sdk and import A2AAdapter:
import { A2AAdapter, Agent } from "npm:@vibesjs/sdk";
import { anthropic } from "npm:@ai-sdk/anthropic";

const model = anthropic("claude-opus-4-5");
const agent = new Agent({ model, name: "Research Agent", systemPrompt: "You are a research assistant." });

Constructor

const adapter = new A2AAdapter(agent, {
  name: "Research Agent",
  description: "Answers research questions using web search and reasoning.",
  url: "https://my-agent.example.com",
  version: "1.0.0",
  skills: [
    {
      id: "research",
      name: "Research",
      description: "Answer factual questions and summarize information",
    },
  ],
  provider: { organization: "Acme Corp", url: "https://acme.example.com" },
  deps: { db: getDb() },
});
A2AAdapterOptions fields:
OptionTypeDefaultDescription
namestringAgent nameDisplayed in AgentCard
descriptionstring-Agent description in AgentCard
urlstring"http://localhost:8000"Public URL of this server
versionstring"1.0.0"Agent version string
skillsA2AAgentSkill[][]Capabilities list for discovery
provider{ organization, url? }-Organization info
depsTDeps-Static dependencies for every agent run
taskStoreTaskStoreMemoryTaskStoreTask persistence backend

Deno HTTP server

Deno.serve(adapter.handler());
// GET  /.well-known/agent.json  → AgentCard (discovery)
// POST /                        → JSON-RPC (tasks/send, message/stream, etc.)
adapter.handler() returns a standard Deno HTTP handler. You can also call adapter.handleRequest(req) directly if you need to integrate into an existing server.

AgentCard discovery

Remote agents and clients discover your server by fetching GET /.well-known/agent.json. The response is an AgentCard object describing your agent’s identity and capabilities.
// Example AgentCard returned by GET /.well-known/agent.json
{
  "name": "Research Agent",
  "description": "Answers research questions",
  "url": "https://my-agent.example.com",
  "version": "1.0.0",
  "capabilities": {
    "streaming": true,
    "pushNotifications": false,
    "stateTransitionHistory": false
  },
  "skills": [
    {
      "id": "research",
      "name": "Research",
      "description": "Answer factual questions and summarize information"
    }
  ],
  "provider": {
    "organization": "Acme Corp",
    "url": "https://acme.example.com"
  },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"]
}
AgentCard fields:
FieldTypeDescription
namestringAgent display name
descriptionstring | undefinedWhat the agent does
urlstringBase URL of the A2A server
versionstringAgent version
capabilitiesobjectSupported features (streaming, pushNotifications, stateTransitionHistory)
skillsA2AAgentSkill[]Array of named capabilities
provider{ organization, url? } | undefinedOrganization info
defaultInputModesstring[]MIME types accepted
defaultOutputModesstring[]MIME types returned
documentationUrlstring | undefinedLink to docs
iconUrlstring | undefinedAgent icon URL

Task lifecycle

Synchronous: message/send

Send a message and wait for the full result. The response is an A2ATask with all output artifacts.
// HTTP request
const response = await fetch("https://my-agent.example.com/", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    method: "message/send",    // also aliased as tasks/send
    id: "req-1",
    params: {
      message: {
        role: "user",
        parts: [{ kind: "text", text: "What are the benefits of Temporal?" }],
      },
    },
  }),
});

const { result } = await response.json();
// result.status.state === "completed"
// result.artifacts[0].parts[0].text === "Temporal provides..."

Streaming: message/stream

Receive incremental updates as the agent processes the request. Returns an SSE stream of events.
const response = await fetch("https://my-agent.example.com/", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    method: "message/stream",  // also aliased as tasks/sendSubscribe
    id: "req-2",
    params: {
      message: {
        role: "user",
        parts: [{ kind: "text", text: "Summarize the history of the web." }],
      },
    },
  }),
});

// Read SSE stream
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const line = decoder.decode(value);
  if (line.startsWith("data: ")) {
    const event = JSON.parse(line.slice(6));
    if (event.kind === "artifact-update") {
      process.stdout.write(event.artifact.parts[0].text);
    }
  }
}

Task state machine

Every task progresses through a defined set of states: A2ATaskState values:
StateDescription
submittedTask created, not yet processing
workingAgent is actively processing
completedAgent finished, artifacts available
failedAn error occurred during processing
canceledTask was canceled via tasks/cancel
input-requiredAgent needs additional user input (not yet fully wired)

SSE streaming events

When using message/stream, the server emits two event types:

status-update

Emitted when the task state changes:
{
  "kind": "status-update",
  "taskId": "task-abc123",
  "contextId": "ctx-xyz",
  "status": {
    "state": "working",
    "message": { "role": "agent", "parts": [] }
  },
  "final": false
}
The final: true flag marks the last event in the stream (state: completed, failed, or canceled).

artifact-update

Emitted for each chunk of streaming output:
{
  "kind": "artifact-update",
  "taskId": "task-abc123",
  "contextId": "ctx-xyz",
  "artifact": {
    "artifactId": "artifact-1",
    "parts": [{ "kind": "text", "text": "Temporal provides durable..." }],
    "index": 0
  }
}

Other JSON-RPC methods

tasks/get

Fetch the current state of a task by ID:
const response = await fetch("https://my-agent.example.com/", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    method: "tasks/get",
    id: "req-3",
    params: { id: "task-abc123" },
  }),
});
const { result } = await response.json();
console.log(result.status.state); // "completed"

tasks/cancel

Cancel a running task. This signals an abort to the SSE stream - any in-flight message/stream response will terminate:
await fetch("https://my-agent.example.com/", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    method: "tasks/cancel",
    id: "req-4",
    params: { id: "task-abc123" },
  }),
});

Task store

By default, A2AAdapter uses MemoryTaskStore - an in-memory implementation that does not persist tasks across process restarts.
import { A2AAdapter, MemoryTaskStore } from "npm:@vibesjs/sdk";

// Default - no need to import MemoryTaskStore for basic usage
const adapter = new A2AAdapter(agent);

// Explicit in-memory store (same as default)
const adapter = new A2AAdapter(agent, {
  taskStore: new MemoryTaskStore(),
});

// Swap for a persistent store in production (implement TaskStore interface):
// const adapter = new A2AAdapter(agent, {
//   taskStore: new RedisTaskStore(redisClient),
// });
MemoryTaskStore is fine for development and stateless serverless deployments. For long-running processes where you need task history across restarts, implement the TaskStore interface backed by a database or Redis.

Message parts

A2A messages are composed of parts. Each part has a kind discriminant:

Text part

{ kind: "text", text: "What is the capital of France?" }

File part

{
  kind: "file",
  file: {
    mimeType: "image/png",
    name: "screenshot.png",
    bytes: "<base64-encoded-bytes>",  // inline bytes
    // OR:
    uri: "https://storage.example.com/screenshot.png",  // remote URL
  }
}

Data part

{ kind: "data", data: { key: "value", count: 42 } }

API reference

A2AAdapterOptions

interface A2AAdapterOptions<TDeps> {
  name?: string;
  description?: string;
  url?: string;          // default: "http://localhost:8000"
  version?: string;      // default: "1.0.0"
  skills?: A2AAgentSkill[];
  provider?: { organization: string; url?: string };
  deps?: TDeps;
  taskStore?: TaskStore;  // default: MemoryTaskStore
}

A2AAdapter

MemberSignatureDescription
Constructornew A2AAdapter(agent, options?)Create the adapter
handler()() => (req: Request) => Promise<Response>Returns a Deno HTTP handler
handleRequest(req)(req: Request) => Promise<Response>Routes GET /.well-known/agent.json and POST /

A2AAgentSkill

FieldTypeDescription
idstringUnique skill identifier
namestringDisplay name
descriptionstring | undefinedWhat the skill does
tagsstring[]Optional tags for filtering
examplesstring[]Example inputs

JSON-RPC Methods

MethodAliasReturnsDescription
message/sendtasks/sendA2ATaskSynchronous - waits for full result
message/streamtasks/sendSubscribeSSE streamStreaming - emits events as agent processes
tasks/get-A2ATaskFetch task by ID
tasks/cancel-A2ATaskCancel a running task