initializdocs
DeveloperAgent SDK

Enable A2A on a claude-agent

Expose an agent's real capabilities as typed A2A skills — scaffold with initializ agent init, implement an @initializ/a2a-kit executor, wire the package.json pointer, and deploy with a2a.enabled.

This guide takes a Claude Agent SDK repository from "reachable by nobody" to a governed A2A agent: the platform serves an Agent Card built from your skills, other agents and workflows invoke them, and every inbound task runs under the same governance and audit as the agent's own model calls. You write plain async functions — the A2A wire format, the server, and the auth are all handled by the governed loader already in your image.

Before you start

  • A Node.js (≥ 18) agent repository built on the Claude Agent SDK, deployed from CI as a claude-agent — if you're not there yet, start with Deploy a CI-built agent and Build a governed image in CI.
  • The initializ CLI locally (the scaffold step runs entirely offline).

Step 1 — scaffold

initializ agent init --name code-reviewer --provider anthropic

This writes the two files A2A needs (existing files are never overwritten without --force):

  • initializ-deploy.yaml — the deploy spec with agent.type: claude-agent and A2A enabled by default.
  • a2a-executor.ts — an @initializ/a2a-kit executor stub with one skill; you fill in its run(). If your project keeps sources under src/, the stub lands at src/a2a-executor.ts so tsc emits it to dist/a2a-executor.js.

If a package.json is present, the scaffold also wires the executor pointer into it automatically. See the agent init reference for flags and validation rules.

Step 2 — install the kit

npm install @initializ/a2a-kit

The kit has zero runtime dependencies — it's types plus two helpers. Input validation is bring-your-own: anything with a .parse method works (any Zod schema qualifies).

Step 3 — check the executor pointer

The loader finds your executor through this package.json field (the scaffold adds it when it can):

"initializ": { "a2a": "./dist/a2a-executor.js" }

The pointer names the compiled file, relative to the working directory the agent runs in. Discovery order: the pointer, then the conventional ./dist/a2a-executor.js. The module's default export must be the executor object — which is exactly what defineAgent returns. If no executor is found, the loader falls back to a generic chat skill that bridges messages to the governed query().

Step 4 — implement the executor

Define one skill per capability. This example (condensed from a real production agent) exposes a PR-review pipeline: validate the input, do the work, return a result object.

src/a2a-executor.ts
import { defineAgent, skill } from "@initializ/a2a-kit";
import { z } from "zod";
import { queue } from "./core/queue/index.js";
import { github } from "./core/clients/github/index.js";

const ReviewInput = z.object({
  repo: z.string().default("sportsbook"),
  pr: z.number().int().positive(),
});

export default defineAgent({
  skills: [
    skill({
      id: "review-pr",
      name: "Review a pull request",
      description:
        "Fetch a pull request and run the multi-agent code review. " +
        "Returns the queued review reference (trace id).",
      tags: ["code-review"],
      input: ReviewInput,                       // runtime validation
      inputSchema: z.toJSONSchema(ReviewInput), // advertised on the Agent Card
      examples: ['{"repo":"sportsbook","pr":123}'],
      run: async ({ repo, pr }, ctx) => {
        const prData = await github.getPR(repo, pr);
        await queue.enqueue({
          prNumber: prData.number,
          repoName: repo,
          commitSha: prData.headSha,
          traceId: ctx.taskId, // correlate the async job with this task
        });
        return { queued: true, repo, pr: prData.number, title: prData.title };
      },
    }),
  ],
});

How tasks map to your handler:

  • Skill selection: callers name a skill via the message metadata's skillId; without one, the defaultSkill (or the first skill) handles the task.
  • Input: run() receives the message's JSON data part as an object, or the message text as a string when there is no data part. When the skill declares input, the value is validated first and a parse failure fails the task with the validation error.
  • Output: return a string for a text reply, an object for a structured data reply (or { text, data } for both).
  • Streaming: ctx.emit?.("chunk") streams incremental text to callers using A2A streaming.
  • Correlation: ctx.taskId is the A2A task id — stamp it on downstream work (queue jobs, trace ids) so async results tie back to the exact invocation.

Step 5 — declare A2A in the deploy spec

The scaffolded initializ-deploy.yaml already carries this; the parts that matter for A2A:

initializ-deploy.yaml
apiVersion: initializ.ai/v1
kind: AgentDeploy

agent:
  name: code-reviewer
  type: claude-agent   # A2A is claude-agent-only

image: registry.initializ.ai/acme/code-reviewer:latest # --image overrides

model:
  provider: anthropic  # platform-managed gateway; no credentials here

a2a:
  enabled: true
  auth: bearer         # the default — callers present a platform token

auth: bearer (the default when the field is empty) verifies every caller's token against the platform and accepts only same-workspace callers, failing closed; auth: none disables the check for local development. port, name, and description optionally override the A2A port (default 9090) and the Agent Card identity — see the A2A exposure reference. To make the card fetchable off-cluster, add an ingress block.

Step 6 — deploy from CI

initializ agent deploy -f initializ-deploy.yaml --image "$GOVERNED_IMAGE"

The image is your CI-built, governance-wrapped image — the A2A server lives in that governance layer, so the wrap step is what makes a2a.enabled do anything. On deploy, the platform opens the A2A port and routing and hands the loader its A2A configuration; the loader discovers your executor via the pointer and starts serving.

What you get

  • An Agent Card built from your skills — served at /.well-known/agent-card.json, listing each skill's id, name, description, tags, examples, and inputSchema, and advertising the bearer auth scheme.
  • Per-task governed invocations — each inbound task is its own audited run with lifecycle events, the caller's verified identity, and cache-inclusive token accounting; see what the platform records for every run.
  • Workflow participation — the agent joins workflows like any platform agent, and its typed input schemas drive both the workflow planner's argument construction and the structured run forms shown when a skill is invoked directly.

Test skills without a server

The kit ships a direct-invocation helper for unit tests — no server, no A2A envelope:

import { invokeSkill } from "@initializ/a2a-kit";
import agent from "./a2a-executor.js";

const result = await invokeSkill(agent, "review-pr", { repo: "sportsbook", pr: 123 });

On this page