Cogitator
Swarms

Swarm 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.

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:

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

Agent Configuration

Different strategies expect agents in different roles:

// 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:

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:

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:

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):

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

Resources and Error Handling

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:

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:

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:

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:

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:

FieldTypeDescription
inputstringThe task to execute
contextRecord<string, unknown>Additional context passed to all agents
saveHistorybooleanWhether to persist conversation history (default true)
threadIdstringBase thread id; each agent uses ${threadId}:${agentName}
userIdstringThe user the swarm acts for; every agent run carries it, so the threads belong to that user
timeoutnumberRejects with SwarmTimeoutError and cancels the run (in-flight LLM calls, further agents) when exceeded
onAgentStart(agent) => voidCalled when an agent turn starts
onAgentComplete(agent, result) => voidCalled when an agent turn completes
onAgentError(agent, error) => voidCalled when an agent turn fails
onMessage(message) => voidCalled for every message sent on the swarm's message bus
onEvent(event) => voidCalled 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:

// 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:

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)}`);

On this page