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'); // requiredAgent 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:
| 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:
// 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)}`);