Cogitator
Swarms

Coordination Strategies

All 7 built-in strategies for multi-agent coordination — hierarchical, round-robin, consensus, auction, pipeline, debate and negotiation.

Overview

Every swarm is driven by a strategy that controls how agents are coordinated. Strategies decide which agents run, in what order, how their outputs combine, and how decisions are reached. There are seven: hierarchical, round-robin, consensus, auction, pipeline, debate and negotiation.

All strategies extend BaseStrategy and implement a single execute(options) method that returns a StrategyResult (output, structured, agentResults plus strategy-specific fields). You configure them through the swarm() builder; createStrategy(coordinator, config) and getDefaultStrategyConfig(strategy) are exported for custom setups.

Consensus, auction, pipeline, debate and negotiation need their configuration block (.consensus({...}), ...) — building the swarm without it throws. Hierarchical and round-robin fall back to defaults.

The snippets below assume a cogitator instance and a model string such as 'openai/gpt-5.5'.

Hierarchical

A supervisor agent receives the task, breaks it down, and delegates subtasks to worker agents. The supervisor coordinates outputs and produces the final result.

import { Agent } from '@cogitator-ai/core';
import { swarm } from '@cogitator-ai/swarms';

const team = swarm('dev-team')
  .strategy('hierarchical')
  .supervisor(
    new Agent({
      name: 'tech-lead',
      model: 'openai/gpt-5.5',
      instructions: 'Break down tasks and delegate to your team members.',
    })
  )
  .workers([
    new Agent({ name: 'frontend-dev', model, instructions: 'Build UI components.' }),
    new Agent({ name: 'backend-dev', model, instructions: 'Build API endpoints.' }),
  ])
  .hierarchical({
    maxDelegationDepth: 3,
    workerCommunication: false,
    routeThrough: 'supervisor',
    visibility: 'full',
  })
  .build(cogitator);
OptionDefaultDescription
maxDelegationDepth3How many levels deep delegation can go (enforced by delegate_task)
workerCommunicationfalseWhether workers can message each other (enforced by the messaging tools)
routeThrough'supervisor''direct' lets workers talk to each other when communication is enabled
visibility'full'Worker output returned to the supervisor: 'full', 'summary', 'none'

The swarm needs a supervisor (.supervisor() or agentMetadata role supervisor). It automatically receives the delegation tools (delegate_task, check_progress, request_revision, list_workers); the supervisor's answer is the swarm output, and workers that ran during the supervisor turn appear in result.agentResults. Give the supervisor a maxIterations large enough for the delegation calls it has to make.

Best for: Complex projects, team-style delegation, coordinated multi-step work.

Round-Robin

Each run() goes to one agent; the next run goes to the next agent in the list. Supports sticky sessions for follow-up routing.

const pool = swarm('support-pool')
  .strategy('round-robin')
  .agents([
    new Agent({ name: 'support-1', model, instructions: 'Handle customer tickets.' }),
    new Agent({ name: 'support-2', model, instructions: 'Handle customer tickets.' }),
    new Agent({ name: 'support-3', model, instructions: 'Handle customer tickets.' }),
  ])
  .roundRobin({
    sticky: true,
    stickyKey: (input) => extractTicketId(String(input)),
    rotation: 'random',
  })
  .build(cogitator);
OptionDefaultDescription
stickyfalseEnable sticky sessions — same key, same agent
stickyKeyundefinedFunction to extract a routing key from the input
rotation'sequential''sequential' or 'random'

The rotation position lives in the Swarm instance, so it carries over between runs. With sticky: true a known stickyKey stays with the agent that handled it first, while a new key takes the next agent of the rotation; without stickyKey every run rotates. The round-robin:assigned event reports each assignment.

Best for: Load balancing, support teams, distributing work evenly.

Consensus

All agents independently evaluate the task and cast votes, round after round, until a decision reaches the threshold or the rounds run out.

const reviewBoard = swarm('review-board')
  .strategy('consensus')
  .agents([
    new Agent({ name: 'security-reviewer', model, instructions: 'Focus on security issues.' }),
    new Agent({ name: 'perf-reviewer', model, instructions: 'Focus on performance.' }),
    new Agent({ name: 'quality-reviewer', model, instructions: 'Focus on code quality.' }),
  ])
  .supervisor(new Agent({ name: 'lead', model, instructions: 'Break ties.' }))
  .consensus({
    threshold: 0.66,
    maxRounds: 3,
    resolution: 'majority',
    onNoConsensus: 'supervisor-decides',
  })
  .build(cogitator);

const result = await reviewBoard.run({ input: 'Should we merge this PR? Vote MERGE or REJECT.' });
OptionRequiredDescription
thresholdyesShare needed to win (0.01 – 1); getDefaultStrategyConfig uses 0.5
maxRoundsyesVoting rounds before onNoConsensus applies
resolutionyes'majority', 'unanimous' or 'weighted'
onNoConsensusyes'fail', 'escalate' or 'supervisor-decides'
weightsnoPer-agent vote weights for 'weighted' (falls back to agentMetadata weight, then 1)
  • Voting. Agents vote by writing a VOTE: <decision> line (a decision: / vote: / choose: / select: line also counts). Voters get the cast_vote, get_votes, change_vote and get_consensus_status tools automatically (the supervisor does not, and distributed swarms attach no tools); a vote recorded with cast_vote for the same round wins over the text. Decisions are compared case-, punctuation- and markdown-insensitively.
  • Winning. majority and weighted pick the leading decision when its share of all eligible voters (or of their total weight) reaches the threshold and it is not tied — abstentions count against it. unanimous needs every voter on the same decision.
  • Rounds. Voters run one after another; from round 2 on they see the previous votes and discussion and can change their position.
  • No consensus. 'fail' throws, 'escalate' returns a report that starts with ESCALATION REQUIRED, and 'supervisor-decides' runs the supervisor (an agent added with .supervisor(), which does not vote) on the votes and discussion; without a supervisor it returns the NO CONSENSUS report.

The output is a report with the decision and the vote history; result.votes maps <agent>_round<n> to { decision, reasoning, weight }, and agentResults uses the same keys. Events: consensus:round, consensus:turn, consensus:vote, consensus:reached.

Best for: Critical decisions, code reviews, risk assessments.

Auction

Agents bid on the task; the winner executes it.

const expertPool = swarm('expert-pool')
  .strategy('auction')
  .agents([
    new Agent({
      name: 'python-expert',
      model: 'ollama/llama3.2',
      instructions: 'Python and data science specialist.',
    }),
    new Agent({
      name: 'typescript-expert',
      model: 'ollama/llama3.2',
      instructions: 'TypeScript and Node.js specialist.',
    }),
  ])
  .agentMetadata({
    'python-expert': { expertise: ['python', 'pandas', 'ml'] },
    'typescript-expert': { expertise: ['typescript', 'node', 'react'] },
  })
  .auction({
    bidding: 'capability-match',
    selection: 'highest-bid',
    minBid: 0.3,
  })
  .build(cogitator);
OptionDefaultDescription
biddingrequired'capability-match' (agents assess themselves) or 'custom' (bidFunction)
bidFunction—(agent: SwarmAgent, task: string) => number | Promise<number>, used with 'custom'
selectionrequired'highest-bid' or 'weighted-random' (chance proportional to the bid)
minBid0Bids below it are discarded

With capability-match, every agent first runs once (in parallel) on a bidding prompt and answers with SCORE: 0.0-1.0, CAPABILITIES: and REASONING: lines; an unparseable score counts as 0.5. That is one extra LLM call per agent. With custom, the swarm calls your function instead — no LLM calls for bidding — and a function that throws bids 0:

const router = swarm('router')
  .strategy('auction')
  .agents([pythonExpert, typescriptExpert])
  .agentMetadata({
    'python-expert': { expertise: ['python', 'pandas'] },
    'typescript-expert': { expertise: ['typescript', 'node'] },
  })
  .auction({
    bidding: 'custom',
    selection: 'highest-bid',
    bidFunction: (agent, task) => {
      const expertise = agent.metadata.expertise ?? [];
      const hits = expertise.filter((skill) => task.toLowerCase().includes(skill)).length;
      return expertise.length > 0 ? hits / expertise.length : 0;
    },
  })
  .build(cogitator);

If no bid reaches minBid, the run throws. The winner's answer is the output; result.bids maps agent names to scores and result.auctionWinner names the winner. Events: auction:start, auction:bid, auction:winner, auction:complete.

Best for: Expert routing, dynamic task assignment, capability-based matching.

Pipeline

Sequential processing through specialized stages. Each stage's output feeds into the next. Stages can act as quality gates that reject bad output and trigger retries. Stages come from pipeline.stages or the top-level stages field (gates and stageInput always come from pipeline); different stages in both places throw.

const contentPipeline = swarm('content-pipeline')
  .strategy('pipeline')
  .pipeline({
    stages: [
      { name: 'research', agent: researcherAgent },
      { name: 'draft', agent: writerAgent },
      { name: 'review', agent: reviewerAgent, gate: true },
      { name: 'polish', agent: editorAgent },
    ],
    gates: {
      review: {
        condition: (output) => !String(output).includes('NEEDS_REVISION'),
        onFail: 'retry-previous',
        maxRetries: 3,
      },
    },
    stageInput: (prevOutput, stage, ctx) => {
      return `Previous output:\n${prevOutput}\n\nOriginal request: ${ctx.input}`;
    },
  })
  .build(cogitator);

A stage is a gate when it has gate: true and an entry in gates under its name; the condition receives the gate stage's own output.

Gate onFail OptionDescription
'abort'Throw and stop the pipeline
'skip'Ignore the failed check and continue with the gate's output
'retry-previous'Re-run the previous stage, then the gate again; after maxRetries failures the pipeline throws
'goto:<stage>'Jump to a named stage (e.g. 'goto:draft'); at most 3 × stages jumps per run

Without stageInput, the first stage gets the swarm input and later stages get the previous output with a short instruction. stageInput receives the previous output, the stage and a PipelineContext (input, stageIndex, stageName, previousOutputs). The last stage's output is the swarm output; result.pipelineOutputs maps each stage name to its output, and agentResults is keyed by stage name. Events: pipeline:stage, pipeline:stage:complete, pipeline:gate:pass, pipeline:gate:fail.

Best for: Content creation, data processing, multi-step refinement.

Debate

Agents argue positions across multiple rounds. An optional moderator synthesizes the arguments into a conclusion.

const debateTeam = swarm('risk-assessment')
  .strategy('debate')
  .agents([
    new Agent({ name: 'advocate', model: 'ollama/llama3.2', instructions: 'Argue IN FAVOR.' }),
    new Agent({ name: 'critic', model: 'ollama/llama3.2', instructions: 'Argue AGAINST.' }),
  ])
  .agentMetadata({ advocate: { role: 'advocate' }, critic: { role: 'critic' } })
  .moderator(
    new Agent({
      name: 'moderator',
      model: 'openai/gpt-5.5',
      instructions: 'Synthesize both sides into a balanced recommendation.',
    })
  )
  .debate({ rounds: 3, format: 'structured', maxTokensPerTurn: 400 })
  .build(cogitator);
OptionDefaultDescription
roundsrequiredNumber of debate rounds (at least 1)
format'structured''structured' asks for clearly structured points; 'freeform' drops that guideline
maxTokensPerTurn—How long a turn's answer may be, in tokens: the output limit of a model that does not reason, and the length debaters are asked to keep to. A turn a reasoning model spent thinking is run once more with room to reason on top
reasoningTokensPerTurn—Output room on top of maxTokensPerTurn from the first try, sparing reasoning models the retry. Unset, a starved turn gets 4096, or twice what it reasoned, on its retry
synthesisPrompt—The moderator's task after the last round instead of the default balanced summary; {topic} and {transcript} are filled in

Providers count a reasoning model's reasoning in the same output limit as its answer, so a short maxTokensPerTurn alone would leave such a model's turn empty. The debate runs a turn again when the model spent it thinking: its answer stopped at the limit, or came back empty after reasoning, with less than half the limit in visible text. That works on every provider, those that report reasoning tokens and those that do not. A model that does not reason keeps the limit as it is, and a turn that called tools is never run again, so its tools do not run twice. Providers cannot cap the answer apart from the reasoning, so with room to reason the answer's length is held by the debaters' instructions, which ask for about three quarters of maxTokensPerTurn in words. With reasoningTokensPerTurn the room is given from the first try:

debate: { rounds: 2, maxTokensPerTurn: 300, reasoningTokensPerTurn: 4000 }

Roles come from agentMetadata (advocate / critic, each with its own instructions); when any agent has one of those roles, only those agents debate. Without roles, all non-moderator agents debate as neutral debaters. At least two debaters are required.

Each round, debaters see previous arguments and refine their positions. Every turn in what debaters and the moderator read is labelled with the speaker's name, and its role when it has one: [optimist (advocate)]: .... The moderator (if present) produces the final output, from its default synthesis task or from synthesisPrompt; otherwise the output lists every agent's arguments by round. result.debateTranscript holds every turn as a SwarmMessage. Events: debate:round, debate:turn.

Best for: Risk assessment, decision analysis, exploring trade-offs.

Negotiation

Multi-party negotiation with structured terms, offers, counteroffers, convergence tracking, and deadlock resolution. The most complex strategy, designed for scenarios where agents must reach binding agreements.

const dealTeam = swarm('contract-negotiation')
  .strategy('negotiation')
  .agents([
    new Agent({
      name: 'buyer',
      model: 'openai/gpt-5.5',
      instructions: 'Get the best deal for the buyer.',
      maxIterations: 8,
    }),
    new Agent({
      name: 'seller',
      model: 'openai/gpt-5.5',
      instructions: 'Get the best deal for the seller.',
      maxIterations: 8,
    }),
  ])
  .negotiation({ maxRounds: 6, onDeadlock: 'arbitrate', maxOffersPerRound: 2 })
  .build(cogitator);

const result = await dealTeam.run({ input: 'Agree on price and delivery date' });
console.log(result.negotiationResult?.outcome, result.negotiationResult?.agreement);

The negotiation proceeds through phases: initialization (declare interests) → proposal (make offers) → counter (accept/reject/counter) → refinement (mediated compromise when stagnating) → agreement, deadlock or escalation. Negotiating agents (every agent that is not a supervisor or moderator; at least two, passed with .agents()) automatically receive the negotiation tools (make_offer, counter_offer, accept_offer, reject_offer, get_negotiation_status, get_current_offers, propose_coalition, join_coalition, declare_interests). Offers carry structured terms (termId, label, value, negotiable, priority 1-10, optional range).

Offers only count when an agent makes them through the tools — terms written in plain text are ignored. A turn typically takes several tool calls (check status, read offers, make or answer an offer), so give negotiating agents enough maxIterations (the agent default is 10; resources.perAgent.maxIterations lowers it). With too few, an agent ends its turn before calling make_offer and the negotiation stalls into a deadlock.

An offer becomes an agreement once every recipient has accepted it with accept_offer (and the agreement satisfies quorum). The first time progress stalls, the strategy publishes a weighted-average compromise as an offer from mediator; agents accept or counter it like any other offer. The negotiation is declared deadlocked when it keeps stagnating after that mediation, when convergence drops below 20% and keeps falling, or when maxRounds run out; onDeadlock then decides the outcome.

OptionDefaultDescription
maxRoundsrequiredMaximum negotiation rounds (10 in DEFAULT_NEGOTIATION_CONFIG)
onDeadlockrequiredWhat a deadlock resolves to ('escalate' in the defaults; see below)
turnOrder'round-robin''round-robin', 'priority' (by weights) or 'dynamic' (most pending offers)
weights—Per-agent weights for turn priority and coalitions (or agentMetadata weight)
maxOffersPerRound3Offers (including counters) an agent may make per round
offerTimeout60000Default offer expiry in ms; expired offers are marked expired
turnTimeout30000Per-turn time limit; a timed-out turn is skipped instead of failing the negotiation
allowCoalitionstrueWhether propose_coalition is allowed
minCoalitionSize2Members needed for a coalition to become active
quorum0.5Share (≤ 1) or count (> 1) of negotiating agents an agreement must include
stagnationThreshold0.05Convergence delta below which a round counts as no progress
maxRoundsWithoutProgress3Rounds without progress before mediation / deadlock
approvalGates[]Approval gates: agreement-reached, high-value-term, coalition-formed, deadlock

onDeadlock values:

ValueOutcome
'escalate'outcome: 'escalated'; a deadlock approval gate, if configured, is asked first
'arbitrate'Averages the numeric terms of the open offers (weighted by term priority) into a binding agreement — outcome: 'arbitrated'; with no open offers, 'deadlock'
'majority-rules'Takes the most common value of each term across the agents' latest offers — outcome: 'agreement', reachedVia: 'majority'
'supervisor-decides'Runs the supervisor (.supervisor()) on the open positions; its answer is in agentResults under <name>_decision, the outcome stays 'deadlock'
'fail'outcome: 'deadlock' — the run returns normally

result.negotiationResult holds outcome, agreement (parties, terms, reachedVia), all offers, coalitions, each agent's finalPositions, the convergenceHistory, rounds and duration; result.output is a readable summary of the same.

Approval gates fire on their trigger when the optional condition holds ('always', 'round>3', 'convergence>0.8', 'parties>2', or 'term:price>=10000'). A request is announced with the negotiation:approval-required event. A gate with timeout resolves through its timeoutAction ('approve', 'reject' or 'escalate') when nobody answers in time; a gate without timeout waits for swarm.respondToApproval(request.id, response). A run timeout or swarm.abort() stops the wait. A rejected agreement is withdrawn while the negotiation continues.

import type { NegotiationApprovalRequest } from '@cogitator-ai/types';

negotiationSwarm.on('negotiation:approval-required', (event) => {
  const { request } = event.data as { request: NegotiationApprovalRequest };
  negotiationSwarm.respondToApproval(request.id, {
    requestId: request.id,
    decision: 'approved',
    approved: true,
    continueNegotiation: false,
    respondedBy: 'legal-team',
    respondedAt: Date.now(),
  });
});

Best for: Contract negotiations, resource allocation, multi-stakeholder decisions.

Strategy Selection Guide

ScenarioStrategy
Team of specialists with a coordinatorhierarchical
Distribute load across identical agentsround-robin
Multiple reviewers must agreeconsensus
Route to the best-suited expertauction
Step-by-step content or data processingpipeline
Evaluate pros and cons of a decisiondebate
Agents must reach a binding agreementnegotiation

On this page