Build a service with a parallel agent team coordinated via A2A
Use the Agent-to-Agent (A2A) protocol to coordinate a multi-agent build team. A team leader runs an A2A hub, dispatches builder agents in two phases — MODELS first (other agents depend on its types), then API + DATABASE in parallel — sharing artifacts through the hub so downstream agents build on real types instead of stubs. Each agent works in its own git worktree, runs tests, and pushes if green. The team leader merges branches and resolves conflicts. All examples use the Claude Agent SDK (@anthropic-ai/claude-agent-sdk) and run with bun.
query() is the single entry point of the SDK. You give it a prompt and options, it spawns a claude CLI subprocess, and returns an AsyncIterable that yields messages as the agent works. The agent loops — reason, tool call, get result, reason again — until it has an answer or hits maxTurns. You consume these messages with a standard for await loop. Each query() call is independent: own subprocess, own context, own tool permissions.
import { query } from '@anthropic-ai/claude-agent-sdk'
query({ prompt, options })
│
├─ Spawns `claude` CLI as a child process
├─ Sends prompt via JSON-RPC over stdin
├─ Claude runs its agentic loop:
│ ├─ { type: 'assistant', message: { content: [...] } }
│ │ content: [{ type: 'text', text: '...' }]
│ │ [{ type: 'tool_use', name: 'Write', input: {...} }]
│ ├─ tool results fed back automatically
│ └─ ... repeats until done or maxTurns hit
├─ Yields each message to your `for await` loop
└─ Final message:
{ type: 'result', result: '...', total_cost_usd: 0.02 }
Options you control:
allowedTools: ['Read', 'Write', 'Edit', 'Bash'] // tool whitelist
maxTurns: 25 // iteration cap
systemPrompt: 'Build the model layer...' // agent role
cwd: './worktrees/models' // working directory (worktree)
permissionMode: 'bypassPermissions' // no tool approval prompts
model: 'sonnet' // model selection
When agents build in parallel, they face a real dependency problem: the API controller needs model types, the repository needs model types, but the MODELS agent is building those types at the same time. Without coordination, downstream agents create stubs that may not match the real types — leading to merge conflicts. The Agent-to-Agent (A2A) protocol solves this by giving the team leader a hub to publish and fetch artifacts between build phases. MODELS builds first, its artifacts go into the hub, and the team leader distributes real types to downstream worktrees before they start building.
// A2A hub sits between build phases:
Phase 1 A2A Hub Phase 2
─────── ─────── ───────
MODELS agent localhost:9100
│ builds types ┌───────────────┐
│ runs tests │ Agent Cards │
│ commits │ ─────────── │
└───────────────────▶│ MODELS │
team leader │ API │
publishes │ DATABASE │
artifacts │ │
│ Artifacts │
│ ─────────── │
│ Payment.java │────▶ API agent
│ Status.java │ │ has real types
│ Request.java │ │ builds controller
│ │ │ no stubs needed
│ │
│ │────▶ DATABASE agent
└───────────────┘ │ has real types
│ builds repository
│ no stubs needed
Each query() spawns its own subprocess — they don't share context or state. Git worktrees give each agent an isolated copy of the repo on its own branch. The two-phase approach means MODELS builds first (it has no dependencies), then merges into main. Phase-2 worktrees branch from the updated main, so model types are in git history — not copied files. Each agent writes integration tests and verifies their work before pushing.
// Two-phase build with git-based type distribution:
main branch (module + Application.java scaffolded)
│
├── worktree: .worktrees/models (feature/models)
│
Phase 1:
┌──────────┐
│ MODELS │ builds types + repo interface, tests, pushes
└────┬─────┘
│
▼
A2A hub: publish MODELS artifacts
│
┌────┴─────────────────────┐
│ merge feature/models │
│ INTO main │
│ (types now in git) │
└────┬─────────────────────┘
│
main (updated — has model types in git history)
│
├── worktree: .worktrees/api (feature/api)
└── worktree: .worktrees/db (feature/database)
Phase 2 (parallel):
┌──────────────┐ ┌──────────────┐
│ API │ │ DATABASE │
│ (model types │ │ (model types │
│ in git │ │ in git │
│ history) │ │ history) │
│ tests POST/ │ │ tests CRUD │
│ GET endpts │ │ operations │
└────┬─────────┘ └────┬─────────┘
│ │
▼ ▼
┌─────────────────────────────┐
│ merge phase-2 only │
│ (feature/models already │
│ merged — clean merges!) │
│ final: ./mvnw test │
└─────────────────────────────┘
Install the SDK with bun and verify the import works.
cd src/data/demo
bun install
bun -e "import { query } from '@anthropic-ai/claude-agent-sdk'; console.log('SDK imported')"
The bun -e command prints 'SDK imported' without errors. The SDK uses your Claude Code login — no ANTHROPIC_API_KEY needed. For production use outside Claude Code, set ANTHROPIC_API_KEY.
Stand up an A2A hub, dispatch builder agents in two phases (MODELS first, then API + DATABASE in parallel with real types from the hub), merge branches, and run the service.
import { query } from "@anthropic-ai/claude-agent-sdk";
import { readFileSync } from "fs";
import { resolve } from "path";
// 2-build-one.ts — Single builder agent.
// Reads a spec file, sends it as context, and lets one agent build a layer.
// Generic: swap the spec file to build anything.
const SPEC_PATH = resolve(import.meta.dir, "sample/spec.md");
const REPO_DIR = resolve(import.meta.dir, "../multi-agent");
const spec = readFileSync(SPEC_PATH, "utf-8");
const systemPrompt = `You are a domain modeler. Build the domain model layer for the project described in the spec below.
Create all model files: records, enums, value objects with proper validation.
Write unit tests for each model.
After building, run the test command from the spec.
If tests pass: git add -A && git commit -m "feat: add domain models"
If tests fail: fix and retry.
Only create files for the model layer — do not build controllers, repositories, or other layers.
## Spec
${spec}`;
console.log("Single builder agent");
console.log(` Spec: ${SPEC_PATH}`);
console.log(` Repo: ${REPO_DIR}\n`);
const q = query({
prompt:
"Build the domain model layer as described in your system prompt. Create all model classes with validation, write tests, run them, and commit if green.",
options: {
systemPrompt,
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
maxTurns: 25,
cwd: REPO_DIR,
permissionMode: "bypassPermissions",
},
});
for await (const msg of q) {
if (msg.type === "assistant" && msg.message) {
for (const block of msg.message.content) {
if (block.type === "tool_use") {
const detail =
block.input?.file_path ??
block.input?.command ??
block.input?.pattern ??
"";
const short =
typeof detail === "string"
? detail.slice(0, 100)
: JSON.stringify(detail).slice(0, 100);
console.log(` [MODELS] ${block.name}(${short})`);
}
}
}
if (msg.type === "result") {
console.log(
`\nDone [${msg.subtype}, $${msg.total_cost_usd.toFixed(4)}]`
);
}
}
import { query } from "@anthropic-ai/claude-agent-sdk";
import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync } from "fs";
import { resolve, relative, join } from "path";
import { execSync } from "child_process";
// 3-build-team.ts — The Build Team with A2A coordination.
// Two-phase build: MODELS first → publish to A2A hub → API + DATABASE in parallel.
const SPEC_PATH = resolve(import.meta.dir, "sample/spec.md");
const REPO_DIR = resolve(import.meta.dir, "../multi-agent");
const spec = readFileSync(SPEC_PATH, "utf-8");
const agents = [
{ name: "MODELS", branch: "feature/models", phase: 1, role: "Build the domain model layer..." },
{ name: "API", branch: "feature/api", phase: 2, role: "Build the REST controller layer..." },
{ name: "DATABASE", branch: "feature/database", phase: 2, role: "Build the repository/storage layer..." },
];
// ── A2A Hub (JSON-RPC 2.0) ─────────────────────────────────────────
const hub = { agents: new Map(), artifacts: new Map() };
// ... handles agent/register, agent/updateStatus, artifacts/publish, artifacts/get
// ── Two-phase flow (git-based distribution) ───────────────────────────────
// Phase 1: MODELS builds first (only MODELS worktree exists)
const modelsResult = await runAgent(modelsAgent);
// Team leader publishes MODELS artifacts to A2A hub
const artifacts = collectJavaFiles(modelsWorktreeSrc);
a2a("artifacts/publish", { agent: "MODELS", artifacts });
// Merge feature/models INTO main — types are now in git history
run("git checkout main");
run("git merge feature/models --no-edit");
// Create phase-2 worktrees from updated main (types in git!)
run("git worktree add .worktrees/api -b feature/api");
run("git worktree add .worktrees/db -b feature/database");
// Build type summary from A2A artifacts for system prompts
const modelsSummary = buildModelFilesSummary(modelsArtifacts);
// Phase 2: API + DATABASE in parallel (model types in git history)
const results = await Promise.allSettled(
phase2Agents.map(a => runAgent(a, modelsSummary))
);
// Merge phase-2 branches only (MODELS already merged), run final tests
// ... (see full source in src/data/demo/3-build-team.ts)
3-build-team.ts (TEAM LEADER)
├─ Reads spec.md
├─ Scaffolds module + Application.java on main
├─ Starts A2A Hub (localhost:9100)
│
├─ Phase 1: MODELS agent builds alone
│ ├─ creates MODELS worktree only
│ ├─ cwd: .worktrees/models
│ ├─ builds: model/ + repository interface
│ └─ IT tests → commit → push
│
├─ A2A: publish MODELS artifacts to hub
├─ Merge feature/models INTO main (types in git history)
│
├─ Phase 2: create worktrees from updated main
│ ├─ API agent (feature/api)
│ │ └─ cwd: .worktrees/api (model types in git)
│ │ └─ builds: controller/ only
│ │ └─ @WebMvcTest: POST/GET endpoints
│ │ └─ IT tests → commit → push
│ └─ DATABASE agent (feature/database)
│ └─ cwd: .worktrees/db (model types in git)
│ └─ builds: InMemoryPaymentRepository only
│ └─ IT tests: CRUD operations
│ └─ IT tests → commit → push
│
├─ Merges phase-2 only (MODELS already merged — clean!)
├─ Runs final tests (./mvnw test)
└─ A2A Hub summary: agents, statuses, artifact counts