Multi-Agent Patterns
Reactive Agents supports multiple agents working together through the orchestration layer. This page shows patterns for common multi-agent architectures.
Research Pipeline
Section titled “Research Pipeline”Three specialized agents work sequentially — each builds on the previous agent’s output:
import { ReactiveAgents } from "reactive-agents";import { OrchestrationService } from "@reactive-agents/orchestration";import { Effect } from "effect";
// Create specialized agentsconst researcher = await ReactiveAgents.create() .withName("researcher") .withProvider("anthropic") .withReasoning() .withTools() .build();
const analyst = await ReactiveAgents.create() .withName("analyst") .withProvider("anthropic") .withReasoning({ defaultStrategy: "reflexion" }) .build();
const writer = await ReactiveAgents.create() .withName("writer") .withProvider("anthropic") .withReasoning({ defaultStrategy: "reflexion" }) .build();
// Orchestrate as a pipelineconst program = Effect.gen(function* () { const orch = yield* OrchestrationService;
const workflow = yield* orch.executeWorkflow( "research-report", "pipeline", [ { id: "1", name: "research", agentId: "researcher", input: "Find recent CRISPR developments" }, { id: "2", name: "analyze", agentId: "analyst", input: "" }, // Gets researcher's output { id: "3", name: "write", agentId: "writer", input: "" }, // Gets analyst's output ], async (step) => { const agent = { researcher, analyst, writer }[step.agentId]; const input = step.input || step.output; // Pipeline chains output → input return await agent.run(input); }, );
return workflow;});Parallel Research
Section titled “Parallel Research”Multiple agents research different angles simultaneously:
const workflow = yield* orch.executeWorkflow( "multi-source-research", "parallel", [ { id: "1", name: "academic", agentId: "scholar", input: "Search academic papers on quantum computing" }, { id: "2", name: "industry", agentId: "analyst", input: "Search industry reports on quantum computing" }, { id: "3", name: "news", agentId: "journalist", input: "Search recent news on quantum computing" }, ], (step) => agents[step.agentId].run(step.input),);
// All three run concurrently — results available when all completeMap-Reduce Analysis
Section titled “Map-Reduce Analysis”Split a large task across workers, then aggregate results:
// Split a large dataset into chunksconst chunks = splitData(largeDataset, 5);
const workflow = yield* orch.executeWorkflow( "distributed-analysis", "map-reduce", [ // Map phase — all run in parallel ...chunks.map((chunk, i) => ({ id: String(i + 1), name: `analyze-${i}`, agentId: "worker", input: `Analyze this data chunk: ${chunk}`, })), // Reduce phase — runs after map completes { id: String(chunks.length + 1), name: "aggregate", agentId: "aggregator", input: "Combine and summarize all analysis results", }, ], (step) => agents[step.agentId].run(step.input),);Orchestrator-Workers with Delegation
Section titled “Orchestrator-Workers with Delegation”A central orchestrator dispatches tasks and delegates specific permissions:
import { IdentityService } from "@reactive-agents/identity";
const program = Effect.gen(function* () { const orch = yield* OrchestrationService; const identity = yield* IdentityService;
// Spawn specialized workers const dataWorker = yield* orch.spawnWorker("data-processing"); const searchWorker = yield* orch.spawnWorker("web-search");
// Delegate search permission to the search worker (1 hour) yield* identity.delegate( "orchestrator", searchWorker.agentId, [{ resource: "tools/web_search", actions: ["execute"] }], "Research subtask", 3600_000, );
// Execute workflow const workflow = yield* orch.executeWorkflow( "managed-research", "orchestrator-workers", [ { id: "1", name: "plan", agentId: "orchestrator", input: "Plan research on AI safety" }, { id: "2", name: "search", agentId: searchWorker.agentId, input: "Search for papers" }, { id: "3", name: "process", agentId: dataWorker.agentId, input: "Process findings" }, { id: "4", name: "synthesize", agentId: "orchestrator", input: "Write final report" }, ], (step) => executeStep(step), );});Durable Workflows with Checkpoints
Section titled “Durable Workflows with Checkpoints”For long-running workflows, use checkpoints to survive crashes:
// Start a workflowconst workflow = yield* orch.executeWorkflow("long-task", "sequential", steps, executeStep);
// If the process crashes, resume from the last checkpoint:const resumed = yield* orch.resumeWorkflow(workflow.id, executeStep);// Only re-executes pending/failed steps — completed steps are skippedAgent Specialization
Section titled “Agent Specialization”Build agents with different capability profiles for different roles:
// Fast, cheap agent for simple classificationconst classifier = await ReactiveAgents.create() .withName("classifier") .withProvider("anthropic") .withModel("claude-3-5-haiku-latest") .build();
// Quality-focused agent for writingconst writer = await ReactiveAgents.create() .withName("writer") .withProvider("anthropic") .withModel("claude-sonnet-4-20250514") .withReasoning({ defaultStrategy: "reflexion" }) .withVerification() .build();
// Tool-using agent for researchconst researcher = await ReactiveAgents.create() .withName("researcher") .withProvider("anthropic") .withReasoning() .withTools() .withMemory("1") .build();
// Full production agent for critical tasksconst seniorAgent = await ReactiveAgents.create() .withName("senior") .withProvider("anthropic") .withModel("claude-sonnet-4-20250514") .withReasoning({ defaultStrategy: "adaptive" }) .withTools() .withMemory("2") .withGuardrails() .withVerification() .withCostTracking() .withObservability() .build();Event-Driven Coordination
Section titled “Event-Driven Coordination”Use the EventBus to coordinate agents through events:
import { EventBus } from "@reactive-agents/core";
const program = Effect.gen(function* () { const bus = yield* EventBus;
// Agent A publishes events yield* bus.publish({ type: "research.complete", agentId: "researcher", data: { findings: "..." }, });
// Agent B subscribes and reacts const events = yield* bus.subscribe("research.complete"); // Process events as they arrive});Monitoring Multi-Agent Systems
Section titled “Monitoring Multi-Agent Systems”Use observability to track the full system:
const agent = await ReactiveAgents.create() .withName("orchestrator") .withProvider("anthropic") .withOrchestration() .withObservability() .withHook({ phase: "complete", timing: "after", handler: (ctx) => { console.log(`Agent ${ctx.agentId} completed in ${ctx.metadata.duration}ms`); console.log(`Cost: $${ctx.cost}, Tokens: ${ctx.tokensUsed}`); return Effect.succeed(ctx); }, }) .build();Each agent in the system gets its own trace, and workflow-level events are logged in the orchestration event log for full auditability.
Dynamic Sub-Agent Spawning
Section titled “Dynamic Sub-Agent Spawning”The .withDynamicSubAgents() builder method enables the spawn-agent built-in tool.
The parent agent can spawn specialist sub-agents at runtime — the model itself decides
when and what to delegate.
const parent = await ReactiveAgents.create() .withName("coordinator") .withProvider("anthropic") .withTools() .withDynamicSubAgents({ maxIterations: 5 }) .build();
// The model can now call spawn-agent tool:// spawn-agent({ task: "Analyze this dataset", role: "Data Analyst" })const result = await parent.run("Analyze this CSV and write a report.");Sub-agents spawn with a clean context window and inherit the parent’s tool
configuration. Recursion depth is limited to 3 by default (MAX_RECURSION_DEPTH).
Sub-agent persona can be specified via the spawn-agent tool parameters:
role: string — e.g., “Data Analyst”, “Code Reviewer”instructions: string — specific behavior instructionstone: string — e.g., “formal”, “concise”
A2A Remote Agent Communication
Section titled “A2A Remote Agent Communication”Agents can communicate across process boundaries using the A2A protocol:
import { ReactiveAgents } from "reactive-agents";import { discoverAgent, findBestAgent } from "@reactive-agents/a2a";import { Effect } from "effect";
// Discover available agents on the networkconst agents = await Effect.runPromise( discoverMultipleAgents([ "https://agent-a.example.com", "https://agent-b.example.com", "https://agent-c.example.com", ]));
// Find the best agent for a research taskconst best = findBestAgent(agents, { skillIds: ["web-search"], tags: ["research"],});
if (best) { console.log(`Delegating to ${best.agent.name} (score: ${best.score})`);
// Register the remote agent as a tool on your coordinator const coordinator = await ReactiveAgents.create() .withName("coordinator") .withProvider("anthropic") .withRemoteAgent("researcher", best.agent.url) .withReasoning() .build();
const result = await coordinator.run("Research the latest in quantum computing");}Exposing Your Agent via A2A
Section titled “Exposing Your Agent via A2A”# Start your agent as an A2A serverrax serve --name my-agent --provider anthropic --port 3000
# Other agents can now discover and call yours at:# http://localhost:3000/.well-known/agent.jsonSee the A2A Protocol docs for complete server/client API details.