# Swarm Builder

URL: https://cogitator.app/docs/swarms/builder

> Build swarms with the fluent SwarmBuilder API or the Swarm class directly.

## The `swarm()` Builder

The recommended way to create swarms is the `swarm()` factory function. It returns a `SwarmBuilder` with a chainable API for configuring every aspect of your swarm.

```typescript
import { Cogitator, Agent } from '@cogitator-ai/core';
import { swarm } from '@cogitator-ai/swarms';

const cogitator = new Cogitator({/* ... */});

const team = swarm('my-team')
  .strategy('hierarchical')
  .supervisor(supervisorAgent)
  .workers([workerA, workerB])
  .resources({ maxConcurrency: 4, tokenBudget: 50_000 })
  .errorHandling({ onAgentFailure: 'retry', retry: { maxRetries: 2, backoff: 'exponential' } })
  .build(cogitator);
```

The `build(cogitator)` call validates the configuration and returns a ready-to-run `Swarm` instance. Every agent needs a `model` unless the Cogitator sets `llm.defaultModel`.

## Builder Methods

### Required

Every swarm needs a name (passed to `swarm()`) and a strategy:

```typescript
swarm('team-name').strategy('hierarchical'); // required
```

### Agent Configuration

Different strategies expect agents in different roles:

```typescript
// hierarchical: supervisor + workers
swarm('dev-team').strategy('hierarchical').supervisor(leadAgent).workers([devAgent, qaAgent]);

// consensus, round-robin, auction: flat agent list
swarm('review-board').strategy('consensus').agents([reviewerA, reviewerB, reviewerC]);

// debate: agents + optional moderator
swarm('debate-team')
  .strategy('debate')
  .agents([advocateAgent, criticAgent])
  .moderator(moderatorAgent);

// negotiation: flat agent list (2+ agents)
swarm('deal').strategy('negotiation').agents([buyerAgent, sellerAgent]);

// pipeline: configured via strategy-specific options
swarm('pipeline').strategy('pipeline').pipeline({
  stages: [/* ... */],
});
```

Swarm metadata (role, expertise, weight, model lock, `priority`, `custom`) is attached by agent name. Slot defaults apply automatically (`supervisor`, `worker`, `moderator`, `router`; pipeline stages get their stage name in `custom`), explicit `agentMetadata` overrides them, and agent names must be unique across all slots:

```typescript
swarm('debate-team')
  .strategy('debate')
  .agents([advocateAgent, criticAgent])
  .agentMetadata({
    [advocateAgent.name]: { role: 'advocate' },
    [criticAgent.name]: { role: 'critic', weight: 2 },
  });
```

### Strategy-Specific Configuration

Each strategy has its own configuration method that matches the strategy name:

```typescript
swarm('team').strategy('hierarchical').hierarchical({
  maxDelegationDepth: 3,
  workerCommunication: false,
  routeThrough: 'supervisor',
  visibility: 'full',
});

swarm('pool')
  .strategy('round-robin')
  .roundRobin({
    sticky: true,
    stickyKey: (input) => extractSessionId(String(input)),
    rotation: 'random',
  });

swarm('board').strategy('consensus').consensus({
  threshold: 0.66,
  maxRounds: 3,
  resolution: 'majority',
  onNoConsensus: 'supervisor-decides',
});

swarm('experts').strategy('auction').auction({
  bidding: 'capability-match',
  selection: 'highest-bid',
  minBid: 0.3,
});

swarm('pipe')
  .strategy('pipeline')
  .pipeline({
    stages: [
      { name: 'research', agent: researchAgent },
      { name: 'write', agent: writerAgent },
      { name: 'review', agent: reviewAgent, gate: true },
    ],
    gates: {
      review: {
        condition: (out) => String(out).includes('APPROVED'),
        onFail: 'retry-previous',
        maxRetries: 2,
      },
    },
  });

swarm('debate').strategy('debate').debate({
  rounds: 3,
  format: 'structured',
});

swarm('deal').strategy('negotiation').agents([buyerAgent, sellerAgent]).negotiation({
  maxRounds: 10,
  turnOrder: 'round-robin',
  onDeadlock: 'arbitrate',
  stagnationThreshold: 0.05,
});
```

### Communication

Configure how agents communicate within the swarm:

```typescript
swarm('team')
  .strategy('hierarchical')
  .messaging({
    enabled: true,
    maxMessageLength: 2000,
    maxMessagesPerTurn: 5,
    maxTotalMessages: 100,
  })
  .blackboardConfig({
    enabled: true,
    sections: { tasks: [], results: {} },
    trackHistory: true,
  })
  .observability({ messageLogging: true, blackboardLogging: true });
```

Both default to enabled, in-memory instances (blackboard history tracked). `messageLogging` and `blackboardLogging` log every message and blackboard write through the Cogitator logger; `message:sent` and `blackboard:write` events are emitted either way, and `tracing: true` logs the spans of every agent run. `messaging.protocol` and `blackboard.locking` are deprecated and have no effect.

To give every agent the built-in messaging and blackboard tools, enable them with `agentTools` (see [Swarm Tools](/docs/swarms/communication#swarm-tools)):

```typescript
swarm('team')
  .strategy('round-robin')
  .agents([agentA, agentB])
  .agentTools({ messaging: true, blackboard: true });
```

### Resources and Error Handling

```typescript
swarm('team')
  .strategy('hierarchical')
  .resources({
    maxConcurrency: 5,
    tokenBudget: 100_000, // per run
    costLimit: 2.0,
    timeout: 300_000,
    perAgent: { maxTokens: 2_000, maxIterations: 5, timeout: 60_000 },
  })
  .errorHandling({
    onAgentFailure: 'retry', // 'retry' | 'failover' | 'skip' | 'abort'
    retry: { maxRetries: 3, backoff: 'exponential', initialDelay: 1000, maxDelay: 30000 },
    failover: { 'primary-coder': 'backup-coder' },
    circuitBreaker: { enabled: true, threshold: 5, resetTimeout: 60_000 },
    partialResults: true,
  });
```

Budgets restart at the beginning of every run and are checked before each agent turn: once `tokenBudget`, `costLimit` or `timeout` (ms since the run started) is used up, the next turn fails with `Swarm resource budget exceeded` (a turn already running finishes). `maxConcurrency` (default 4) limits parallel phases such as auction bidding, and `perAgent` caps each agent's `maxTokens`, `maxIterations` and per-turn `timeout`.

`retry` makes `maxRetries` additional attempts with the configured backoff; `failover` follows the mapping until an agent succeeds (cycles are detected); `skip` turns a failed turn into an empty result; `abort` (and no `errorHandling`) rethrows. `partialResults` (or `skip`) lets parallel phases return the agents that succeeded. The circuit breaker counts failures across the whole swarm and rejects turns while open.

### Distributed Mode

Enable Redis-backed distributed coordination:

```typescript
swarm('distributed-team')
  .strategy('hierarchical')
  .distributed({
    enabled: true,
    redis: { host: 'localhost', port: 6379 },
    queue: 'swarm-agent-jobs',
    timeout: 300_000,
  });
```

### Assessment

Enable automatic model selection with the assessor:

```typescript
swarm('smart-team')
  .strategy('hierarchical')
  .withAssessor({
    preferLocal: true,
    minCapabilityMatch: 0.3,
    maxCostPerRun: 0.5,
    enabledProviders: ['ollama', 'openai', 'anthropic'],
  });
```

## The `Swarm` Class

If you prefer imperative construction, use the `Swarm` class directly:

```typescript
import { Swarm } from '@cogitator-ai/swarms';

const team = new Swarm(cogitator, {
  name: 'dev-team',
  strategy: 'hierarchical',
  supervisor: leadAgent,
  workers: [devAgent, qaAgent],
  resources: { maxConcurrency: 4 },
});
```

The constructor validates configuration immediately and throws on an unknown strategy, a hierarchical swarm without `supervisor`, a pipeline without stages or with different stages in `stages` and `pipeline.stages`, `agentTools` in a distributed swarm or with the message bus / blackboard disabled, a missing `consensus` / `auction` / `debate` / `negotiation` block, or a consensus or negotiation swarm with fewer than two `agents`. The remaining agent requirements (two debaters, two voters besides the supervisor, ...) are checked when the strategy runs.

## Running a Swarm

Both `Swarm` and the builder result expose the same API:

```typescript
const result = await team.run({
  input: 'Build a REST API for user management',
  context: { language: 'typescript', framework: 'express' },
  saveHistory: false,
  timeout: 120_000,
  onAgentComplete: (agent, run) => console.log(agent, run.usage.totalTokens),
});

console.log(result.output);
console.log(result.agentResults);
```

The `SwarmRunOptions`:

| Field             | Type                      | Description                                                                                              |
| ----------------- | ------------------------- | -------------------------------------------------------------------------------------------------------- |
| `input`           | `string`                  | The task to execute                                                                                      |
| `context`         | `Record<string, unknown>` | Additional context passed to all agents                                                                  |
| `saveHistory`     | `boolean`                 | Whether to persist conversation history (default `true`)                                                 |
| `threadId`        | `string`                  | Base thread id; each agent uses `${threadId}:${agentName}`                                               |
| `userId`          | `string`                  | The user the swarm acts for; every agent run carries it, so the threads belong to that user              |
| `timeout`         | `number`                  | Rejects with `SwarmTimeoutError` and cancels the run (in-flight LLM calls, further agents) when exceeded |
| `onAgentStart`    | `(agent) => void`         | Called when an agent turn starts                                                                         |
| `onAgentComplete` | `(agent, result) => void` | Called when an agent turn completes                                                                      |
| `onAgentError`    | `(agent, error) => void`  | Called when an agent turn fails                                                                          |
| `onMessage`       | `(message) => void`       | Called for every message sent on the swarm's message bus                                                 |
| `onEvent`         | `(event) => void`         | Called for every swarm event during this run                                                             |

A `Swarm` instance runs one task at a time — concurrent `run()` calls on the same instance are rejected. A run that exceeds `timeout` rejects with `SwarmTimeoutError` (exported). A run that times out or fails is cancelled as a whole: in-flight agent calls get the abort signal, and whatever its strategy still has in flight starts no further agents or retries — not after `run()` has rejected, and not inside the next run of the same swarm.

## Swarm Instance API

Once built, a `Swarm` provides these methods:

```typescript
// execution
await team.run({ input: '...' });

// introspection
team.name;
team.id;
team.strategyType;
team.getAgents();
team.getAgent('frontend-dev');
team.getResourceUsage();
team.getLastAssessment();

// events
const unsub = team.on('agent:complete', (event) => {
  /* ... */
});
team.once('swarm:complete', (event) => {
  /* ... */
});

// control
team.pause(); // the next agent turn waits until resume()
team.resume();
team.abort(); // also cancels in-flight LLM calls
team.respondToApproval(requestId, response); // negotiation approval gates
await team.reset(); // clears state, messages and the blackboard; needed after abort()

// distributed cleanup
await team.close();
```

## Dry Run

Preview model assignments without executing the swarm:

```typescript
const team = swarm('team')
  .strategy('hierarchical')
  .supervisor(leadAgent)
  .workers([devAgent, qaAgent])
  .withAssessor({ preferLocal: true })
  .build(cogitator);

const assessment = await team.dryRun({ input: 'Build a REST API' });

for (const assignment of assessment.assignments) {
  console.log(`${assignment.agentName}: ${assignment.assignedModel} (score: ${assignment.score})`);
}

console.log(`Estimated cost: $${assessment.totalEstimatedCost.toFixed(4)}`);
```
