---
title: Multi-Agent Patterns
description: Patterns for building multi-agent systems — pipelines, map-reduce, orchestrator-workers, and delegation.
sidebar:
  order: 4
---

Reactive Agents supports multiple agents working together through the orchestration layer. This page shows patterns for common multi-agent architectures.

## Research Pipeline

Three specialized agents work sequentially — each builds on the previous agent's output:

```typescript
import { ReactiveAgents } from "reactive-agents";
import { OrchestrationService } from "@reactive-agents/orchestration";
import { Effect } from "effect";

// Create specialized agents
const 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 pipeline
const 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

Multiple agents research different angles simultaneously:

```typescript
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 complete
```

## Map-Reduce Analysis

Split a large task across workers, then aggregate results:

```typescript
// Split a large dataset into chunks
const 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

A central orchestrator dispatches tasks and delegates specific permissions:

```typescript
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

For long-running workflows, use checkpoints to survive crashes:

```typescript
// Start a workflow
const 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 skipped
```

## Agent Specialization

Build agents with different capability profiles for different roles:

```typescript
// Fast, cheap agent for simple classification
const classifier = await ReactiveAgents.create()
  .withName("classifier")
  .withProvider("anthropic")
  .withModel("claude-haiku-4-5")
  .build();

// Quality-focused agent for writing
const writer = await ReactiveAgents.create()
  .withName("writer")
  .withProvider("anthropic")
  .withModel("claude-sonnet-4-20250514")
  .withReasoning({ defaultStrategy: "reflexion" })
  .withVerification()
  .build();

// Tool-using agent for research
const researcher = await ReactiveAgents.create()
  .withName("researcher")
  .withProvider("anthropic")
  .withReasoning()
  .withTools()
  .withMemory()
  .build();

// Full production agent for critical tasks
const seniorAgent = await ReactiveAgents.create()
  .withName("senior")
  .withProvider("anthropic")
  .withModel("claude-sonnet-4-20250514")
  .withReasoning({ defaultStrategy: "adaptive" })
  .withTools()
  .withMemory({ tier: "enhanced" })
  .withGuardrails()
  .withVerification()
  .withCostTracking()
  .withObservability()
  .build();
```

## Event-Driven Coordination

Use the EventBus to coordinate agents through events:

```typescript
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

Use observability to track the full system:

```typescript
import { Effect } from "effect";
import { ReactiveAgents } from "reactive-agents";

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

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.

```typescript
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 instructions
- `tone`: string — e.g., "formal", "concise"

## A2A Remote Agent Communication

Agents can communicate across process boundaries using the A2A protocol:

```typescript
import { ReactiveAgents } from "reactive-agents";
import { discoverAgent, findBestAgent } from "@reactive-agents/a2a";
import { Effect } from "effect";

// Discover available agents on the network
const 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 task
const 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

```bash
# Start your agent as an A2A server
rax serve --name my-agent --provider anthropic --port 3000

# Other agents can now discover and call yours at:
# http://localhost:3000/.well-known/agent.json
```

See the [A2A Protocol](/features/a2a-protocol/) docs for complete server/client API details.
