a2a-wrapper logoa2a-wrapper
ReferencePackages

@a2a-wrapper/core

API reference for the shared core: config loading, server factory, sessions, events, memory, and sub-agents.

This page is generated from packages/core/README.md and stays in sync with the repository.

Shared infrastructure core for A2A protocol wrapper projects. Provides logging, configuration loading, event publishing, agent card building, server bootstrapping, session management, and CLI scaffolding — so each wrapper only needs to implement its backend-specific executor. All A2A protocol/spec-version-specific logic lives here and nowhere else in this monorepo — see Protocol Versions.

Installation

npm install @a2a-wrapper/core

Peer dependencies (your wrapper project must install these):

npm install @a2a-js/sdk express uuid

Release Channels

ChannelInstallCadence
Stablenpm install @a2a-wrapper/coreManually reviewed and approved — see Release Process
Canarynpm install @a2a-wrapper/core@canaryEvery merge to main that passes CI

Canary builds publish automatically after each merge, tagged like @a2a-wrapper/[email protected]. They're unreviewed and have no changelog entry — useful for trying a fix ahead of a stable release, not for production. npm install @a2a-wrapper/core always resolves to the latest stable release regardless of how many canaries have shipped since.

Quick Start

A minimal wrapper project using createCli:

import {
  createCli,
  type BaseAgentConfig,
  type A2AExecutor,
} from "@a2a-wrapper/core";

// 1. Define your backend-specific config
interface MyBackendConfig {
  apiUrl: string;
}
type MyConfig = BaseAgentConfig<MyBackendConfig>;

// 2. Implement the executor interface
class MyExecutor implements A2AExecutor {
  constructor(private config: Required<MyConfig>) {}
  async initialize() { /* connect to backend */ }
  async shutdown() { /* cleanup */ }
  async execute(context: any, event: any) { /* handle A2A tasks */ }
}

// 3. Wire it up with createCli
createCli<MyConfig>({
  packageName: "my-a2a-wrapper",
  version: "1.0.0",
  defaults: { /* full default config */ } as Required<MyConfig>,
  usage: "Usage: my-a2a-wrapper [options]",
  executorFactory: (config) => new MyExecutor(config),
  parseBackendArgs: (values) => ({
    backend: { apiUrl: values["api-url"] as string },
  }),
  loadEnvOverrides: () => ({
    backend: { apiUrl: process.env.MY_API_URL },
  }),
  extraArgDefs: {
    "api-url": { type: "string" },
  },
});

Run it:

node dist/cli.js --port 3000 --log-level debug --api-url http://localhost:8080

Protocol Versions

createA2AServer() speaks A2A v1.0 natively and is fully backward compatible with v0.3.x clients out of the box — there is nothing to configure. This is built on @a2a-js/sdk@^1.0.0's own compat/v0_3 translation layer, not a hand-rolled shim.

How negotiation works, per request:

  • buildAgentCard() produces the native v1.0 AgentCard shape — supportedInterfaces[] (not the old url/additionalInterfaces/protocolVersion fields), with both 0.3 and 0.3.0 legacy aliases per transport binding. This preserves compatibility with the SDK's canonical 0.3 value and this project's historically advertised 0.3.0 value.
  • GET /.well-known/agent-card.json reads the caller's A2A-Version request header: 1.0 returns the native v1.0 card; 0.3, 0.3.0, or no header at all returns a fully v0.3-shaped legacy card (url, additionalInterfaces, protocolVersion: "0.3.0"), so old clients keep working unmodified.
  • POST /a2a/jsonrpc and the HTTP+JSON routes below /a2a/rest accept their native v1.0 shapes and transparently translate v0.3 requests and responses. JSON-RPC uses legacy methods such as message/send and message/stream; HTTP+JSON uses legacy routes such as /a2a/rest/v1/message:send. No separately configured v0.3 server is required.

Signed Agent Cards (A2A v1.0, JWS per RFC 7515) are supported as an opt-in extra — pass ServerOptions.agentCardSigning (or set AgentCardConfig.signing in your resolved config, reading the private key from an env-var-held JWK) and AgentCard.signatures gets populated automatically. Unsigned cards (the default) simply omit signatures.

import { createA2AServer } from "@a2a-wrapper/core";

// protocolVersion here is only the informational A2A-Version response
// header value — it does not gate which wire format is actually served;
// that's negotiated per-request as described above.
const handle = await createA2AServer(config, executorFactory, {
  protocolVersion: "1.0", // default
});

API Reference

All public symbols are exported from the package root (@a2a-wrapper/core). Imports from internal module paths are not supported.

Utils

ExportDescription
createLogger(rootName)Factory that returns a new Logger instance with the given root name.
LoggerStructured logger with debug, info, warn, error methods and child(name) for hierarchical naming.
LogLevelEnum — DEBUG, INFO, WARN, ERROR.
createDeferred<T>()Returns a Deferred<T> with externally-resolvable promise, resolve, and reject.
sleep(ms)Returns a Promise that resolves after ms milliseconds.
deepMerge(target, source)Recursively merges objects. Arrays are replaced, inputs are not mutated.
substituteEnvTokens(args)Replaces $VAR_NAME tokens in string arrays with matching env var values.

Config

ExportDescription
BaseAgentConfig<TBackend>Generic config interface — includes agentCard, server, session, logging, timeouts, and backend: TBackend.
AgentCardConfigAgent card fields (name, description, skills, capabilities).
ServerConfigServer fields (port, hostname, advertiseHost, advertiseProtocol).
SessionConfigSession fields (reuseByContext, ttlMs, cleanupIntervalMs).
BaseFeatureFlagsShared feature flags (streamArtifactChunks).
TimeoutConfigTimeout settings.
LoggingConfigLogging settings (level).
BaseMcpServerConfigCommon MCP server config pattern.
EventsConfigEvent transport config — enabled, transport, httpUrl, httpTimeout, httpHeaders.
SkillConfigSkill definition (id, name, description, tags, examples).
loadConfigFile<T>(filePath)Reads and parses a JSON config file. Throws descriptive errors on failure.
resolveConfig<T>(defaults, configFilePath?, envOverrides?, cliOverrides?)Merges config layers: defaults ← file ← env ← CLI.

Events

ExportDescription
publishTask(bus, taskId, contextId)Registers a bare Task with the A2A result manager before any status events. Call once at the start of execute() when no prior task record exists.
publishStatus(bus, taskId, contextId, state, messageText?, final?)Publishes a TaskStatusUpdateEvent.
publishFinalArtifact(bus, taskId, contextId, text)Publishes a complete artifact (lastChunk: true).
publishStreamingChunk(bus, taskId, contextId, artifactId, chunkText)Publishes an appending artifact chunk.
publishLastChunkMarker(bus, taskId, contextId, artifactId, fullText)Publishes the final streaming chunk.
publishTraceArtifact(bus, taskId, contextId, traceKey, data)Publishes a structured DataPart trace artifact.
publishThoughtArtifact(bus, taskId, contextId, traceKey, text)Publishes a TextPart trace artifact.

Event Transport

Pluggable transport layer for sideband observability events (MCP tool calls, agent reasoning, lifecycle). Decouples event emission from A2A protocol internals so you can route trace data to any backend.

ExportDescription
A2ATransportDefault transport — publishes trace artifacts on the A2A ExecutionEventBus. Zero dependencies.
HttpTransportPOST events as JSON to a configurable HTTP endpoint. Supports custom headers for auth.
AgentEventEmitterPer-execution emitter that stamps events with agent identity, trace context, UUID, and timestamp.
resolveTransport(cfg, bus, taskId, contextId, custom?)Resolve the correct transport for a single task execution.
createTransport(cfg)Create a built-in transport from JSON config.
wrapTransport(transport)Normalize a function or object into an EventTransport.
EventTransportInterface — implement send(event) for custom transports.
EventTransportFnConvenience type — a plain async (event) => void function.
AgentEventStructured event with eventId, eventType, agentId, traceId, data.
EventTypeUnion: tool_call_start, tool_call_end, thinking, decision, agent_started, agent_finished, agent_error.
TRACE_EXTENSION_URIConstant "urn:x-a2a:trace:v1" — declared in agent card capabilities and on trace artifacts.
EventsConfigConfig interface — enabled, transport ("a2a" or "http"), httpUrl, httpTimeout, httpHeaders.

Built-in transports

TransportConfig valueDescription
A2A sideband"a2a" (default)Publishes trace artifacts on the ExecutionEventBus. Orchestrators discover them via the urn:x-a2a:trace:v1 extension.
HTTP collector"http"POSTs each event as JSON to httpUrl. Supports httpHeaders for Bearer tokens / API keys.

JSON config

{
  "events": {
    "enabled": true,
    "transport": "http",
    "httpUrl": "https://telemetry.example.com/events",
    "httpTimeout": 5000,
    "httpHeaders": {
      "Authorization": "Bearer ${TELEMETRY_TOKEN}"
    }
  }
}

Custom transport (programmatic API)

For Kafka, Redis, databases, or any custom sink — pass a transport function to createA2AServer():

import { createA2AServer } from "@a2a-wrapper/core";

const handle = await createA2AServer(config, executorFactory, {
  eventTransport: async (event) => {
    await kafkaProducer.send({
      topic: "agent-traces",
      messages: [{ value: JSON.stringify(event) }],
    });
  },
});

Or implement the EventTransport interface for full control:

import type { EventTransport, AgentEvent } from "@a2a-wrapper/core";

class MyTransport implements EventTransport {
  async send(event: AgentEvent): Promise<void> {
    await db.insert("events", {
      id: event.eventId,
      type: event.eventType,
      agent: event.agentId,
      trace: event.traceId,
      data: event.data,
      ts: event.timestamp,
    });
  }
}

Using the emitter inside an executor

import { resolveTransport, AgentEventEmitter } from "@a2a-wrapper/core";

// Inside your executor's execute() method:
const transport = resolveTransport(config.events, bus, taskId, contextId);
const emitter = new AgentEventEmitter({
  agentId: "my-agent",
  agentName: "My Agent",
  traceId: ctx.contextId,
  transport,
});

// Emit events — they flow through the resolved transport
await emitter.emit("tool_call_start", { toolName: "read_file", arguments: { path: "/data.json" } });
await emitter.emit("tool_call_end", { toolName: "read_file", result: "...", durationMs: 42 });
await emitter.emit("thinking", { content: "The data shows a spike in CPU usage..." });

Server

ExportDescription
buildAgentCard(config)Constructs an A2A AgentCard from AgentCardConfig + ServerConfig.
createA2AServer<T>(config, executorFactory, options?)Creates an Express app with standard A2A routes and starts listening. Returns a ServerHandle.
ServerOptionsOptions for protocol version, custom route hooks, and custom eventTransport.
ServerHandleReturned by createA2AServer — contains app, server, executor, eventTransport, shutdown().

Session

ExportDescription
BaseSessionManager<TSession>Abstract class managing contextId → session mapping with TTL cleanup and task tracking. Subclass and implement getOrCreate(contextId).
SessionEntry<TSession>Interface for session entries with session and lastUsed fields.

Executor

ExportDescription
A2AExecutorInterface contract for backend executors — initialize(), shutdown(), execute(), and optional cancelTask(), getContextContent(), buildContext().

CLI

ExportDescription
createCli<T>(options)Main entry point for wrapper CLIs. Handles arg parsing, config resolution, server creation, and graceful shutdown.
CliOptions<T>Configuration for createCli — package name, version, defaults, usage, executor factory, arg definitions.
parseCommonArgs<T>(argv, extraArgDefs?)Parses common CLI flags (--port, --hostname, --log-level, etc.) into typed config overrides.
CommonArgsResult<T>Result of parseCommonArgs — config path and partial overrides.

Memory Persistence

Materialize instructions and skills into the agent's workspace at startup. The LLM reads these files as part of its context, giving it persistent knowledge across sessions.

ExportDescription
materializeMemory(options)Main entry point — reads source files, validates manifests, writes to workspace.
parseSkillManifest(content)Parse a SKILL.md file into frontmatter + body.
formatSkillManifest(frontmatter, body)Serialize frontmatter + body back to SKILL.md format.
validateSkillManifest(manifest)Validate name (kebab-case, ≤64 chars) and description. Returns null if valid.
resolveMemoryPath(inputPath, configDir)Resolve relative/absolute paths against the config directory.
WELL_KNOWN_PATHSPre-defined backend path mappings (copilot, claude, opencode, codex).
MemoryConfigType — { instructions?: string; skills?: string[] }
SkillManifestType — parsed SKILL.md frontmatter (name, description, license, compatibility, allowedTools).
ParsedSkillType — result of parsing: { manifest, body, rawFrontmatter }.
BackendPathsType — { instructionsPath: string; skillsBaseDir: string }.
MaterializeOptionsType — input to materializeMemory().

Config example

{
  "memory": {
    "instructions": "./memory/instructions.md",
    "skills": ["./memory/skills/code-review", "./memory/skills/testing"]
  }
}

SKILL.md format

---
name: code-review
description: Provides code review guidelines and checklists
license: MIT
compatibility:
  - copilot
  - opencode
allowed-tools:
  - read_file
  - search_files
---

# Code Review Skill

Instructions for the LLM on how to perform code reviews...

Skill directory structure

memory/skills/code-review/
├── SKILL.md              # Required: manifest + instructions
├── scripts/              # Optional: executable scripts
├── references/           # Optional: reference documents
└── assets/               # Optional: static assets

Backend path mapping

BackendInstructions pathSkills base dir
Copilot.github/copilot-instructions.md.github/skills/
ClaudeCLAUDE.md.claude/skills/
OpenCode.opencode/instructions.md.opencode/skills/
Codex.codex/instructions.md.agents/skills/
AntigravityAGENTS.md.antigravity/skills/

Usage in an executor

import { materializeMemory, WELL_KNOWN_PATHS } from "@a2a-wrapper/core";

// In executor.initialize():
if (config.memory && workspaceDir) {
  await materializeMemory({
    memoryConfig: config.memory,
    configDir: config.configDir ?? process.cwd(),
    workspaceDir,
    paths: WELL_KNOWN_PATHS.copilot, // or wrapper-specific BackendPaths such as Antigravity's
  });
}

Note on agentCard.skills vs memory.skills

These are different concepts:

  • agentCard.skills — A2A protocol metadata advertised to orchestrators and callers. External-facing. Describes what the agent can do at a high level for discovery and routing.
  • memory.skills — Internal instructions materialized into the workspace for the LLM. Never exposed externally. Tells the LLM how to do things — patterns, guidelines, tool usage rules.

Keep both in sync manually. You may want different descriptions: the agent card skill is marketing-friendly for orchestrators, while the memory skill is technical and detailed for the LLM.

Sub-Agents

Expose remote A2A agents as MCP tools to the parent agent's underlying LLM runtime by spawning a2a-mcp-skillmap (GitHub) as a stdio child process. The parent reads a subAgents array from its config, generates a bridge configuration for skillmap, and registers the bridge as an MCP server before the executor handles its first request.

See .kiro/specs/a2a-subagents/ for the full spec (requirements, design, and tasks). See the a2a-mcp-skillmap GitHub repo for the bridge's own documentation — config schema, response modes, session continuity, sync budget, OpenTelemetry, and HTTP transport.

ExportDescription
bootstrapSubAgents(input)Single entry point — runs validate → build → write → probe → synthesize and returns the canonical MCP descriptor.
validateSubAgents(agents, reservedKeys)Fail-fast checks (name uniqueness, URL shape, reserved-key collision) plus env-var substitution on auth.token.
buildBridgeConfig(source)Produce the JSON document a2a-mcp-skillmap consumes.
resolveBridgeConfigPath(workspaceDir)Compute <workspace>/.a2a/subagents-bridge.json (or a tmpdir fallback).
writeBridgeConfig(config, path)Write the bridge config with mode 0600, creating intermediate directories.
probeSubAgents(agents, timeoutMs)Parallel reachability probes, never throws — one ProbeResult per agent.
buildSynthesizedMcpEntry(path)Produce the canonical, wrapper-agnostic { command, args } MCP descriptor.
SubAgentsConfigTop-level config type — { agents: SubAgentConfig[]; options?: SubAgentsOptions }.
SubAgentsOptionsBridge-wide options — responseMode, probeTimeoutMs, syncBudgetMs (sync budget passed to skillmap).
SubAgentConfigOne sub-agent — { name, agentCardUrl, endpointUrlOverride?, auth? }.
SubAgentAuthConfigOutbound auth — none, bearer, or api_key (with optional headerName).
SynthesizedMcpDescriptorThe wrapper-agnostic descriptor each wrapper translates to its own MCP entry shape.
SUBAGENTS_MCP_KEYReserved MCP map key — "a2a-subagents".
SKILLMAP_PACKAGE_VERSIONPinned a2a-mcp-skillmap version invoked via npx. Bumping it is a deliberate, reviewable change.
SubAgentValidationErrorThrown by validateSubAgents for every fail-fast case, with structured details.

Config example

{
  "subAgents": {
    "agents": [
      {
        "name": "coding",
        "agentCardUrl": "https://coding.example.com/.well-known/agent-card.json",
        "auth": { "mode": "bearer", "token": "${CODING_AGENT_TOKEN}" }
      },
      {
        "name": "research",
        "agentCardUrl": "https://research.example.com/",
        "endpointUrlOverride": "https://research.internal.local/.well-known/agent-card.json"
      }
    ],
    "options": { "responseMode": "artifact", "probeTimeoutMs": 5000, "syncBudgetMs": 30000 }
  }
}

Pinned skillmap version

The MCP entry invokes npx -y a2a-mcp-skillmap@<SKILLMAP_PACKAGE_VERSION> — never the unpinned package name — so a future skillmap release cannot silently change semantics. Bumping the pin is reviewed in PR.

Wrapper integration

Each wrapper provides a tiny adapter that maps the canonical SynthesizedMcpDescriptor into its own MCP entry shape (McpStdioServerConfig for a2a-copilot, McpLocalServerConfig for a2a-opencode), then merges the result into the resolved mcp map under descriptor.key:

import { bootstrapSubAgents } from "@a2a-wrapper/core";

if (this.config.subAgents?.agents?.length) {
  const result = await bootstrapSubAgents({
    subAgents: this.config.subAgents,
    workspaceDir,
    parentLogLevel: this.config.logging.level ?? "info",
    existingMcpKeys: new Set(Object.keys(this.config.mcp ?? {})),
  });
  this.config.mcp = {
    ...(this.config.mcp ?? {}),
    [result.descriptor.key]: this.toWrapperMcpEntry(result.descriptor),
  };
}

A2A SDK Re-exports

ExportSource
AgentCard@a2a-js/sdk
TaskState, TaskStatusUpdateEvent, TaskArtifactUpdateEvent@a2a-js/sdk
ExecutionEventBus, RequestContext@a2a-js/sdk/server

These re-exports isolate wrapper projects from direct SDK imports, so a major SDK upgrade only requires changes in @a2a-wrapper/core.

Contributing

Prerequisites

  • Node.js ≥ 20
  • npm

Development

# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

# Type-check without emitting
npm run typecheck

# Clean build output
npm run clean

Code Standards

  • TypeScript with strict: true
  • JSDoc on every exported symbol
  • Property-based tests (fast-check) alongside unit tests (vitest)
  • Follow Keep a Changelog for CHANGELOG.md

On this page