# Coordination Strategies

URL: https://cogitator.app/docs/swarms/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.

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

| Option                | Default        | Description                                                               |
| --------------------- | -------------- | ------------------------------------------------------------------------- |
| `maxDelegationDepth`  | `3`            | How many levels deep delegation can go (enforced by `delegate_task`)      |
| `workerCommunication` | `false`        | Whether 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.

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

| Option      | Default        | Description                                      |
| ----------- | -------------- | ------------------------------------------------ |
| `sticky`    | `false`        | Enable sticky sessions — same key, same agent    |
| `stickyKey` | `undefined`    | Function 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.

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

| Option          | Required | Description                                                                              |
| --------------- | -------- | ---------------------------------------------------------------------------------------- |
| `threshold`     | yes      | Share needed to win (0.01 – 1); `getDefaultStrategyConfig` uses `0.5`                    |
| `maxRounds`     | yes      | Voting rounds before `onNoConsensus` applies                                             |
| `resolution`    | yes      | `'majority'`, `'unanimous'` or `'weighted'`                                              |
| `onNoConsensus` | yes      | `'fail'`, `'escalate'` or `'supervisor-decides'`                                         |
| `weights`       | no       | Per-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.

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

| Option        | Default  | Description                                                                            |
| ------------- | -------- | -------------------------------------------------------------------------------------- |
| `bidding`     | required | `'capability-match'` (agents assess themselves) or `'custom'` (`bidFunction`)          |
| `bidFunction` | —        | `(agent: SwarmAgent, task: string) => number \| Promise<number>`, used with `'custom'` |
| `selection`   | required | `'highest-bid'` or `'weighted-random'` (chance proportional to the bid)                |
| `minBid`      | `0`      | Bids 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`:

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

```typescript
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` Option | Description                                                                                     |
| -------------------- | ----------------------------------------------------------------------------------------------- |
| `'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.

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

| Option                   | Default        | Description                                                                                                                                                                                                                     |
| ------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rounds`                 | required       | Number 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:

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

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

| Option                     | Default         | Description                                                                            |
| -------------------------- | --------------- | -------------------------------------------------------------------------------------- |
| `maxRounds`                | required        | Maximum negotiation rounds (`10` in `DEFAULT_NEGOTIATION_CONFIG`)                      |
| `onDeadlock`               | required        | What 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`)       |
| `maxOffersPerRound`        | `3`             | Offers (including counters) an agent may make per round                                |
| `offerTimeout`             | `60000`         | Default offer expiry in ms; expired offers are marked `expired`                        |
| `turnTimeout`              | `30000`         | Per-turn time limit; a timed-out turn is skipped instead of failing the negotiation    |
| `allowCoalitions`          | `true`          | Whether `propose_coalition` is allowed                                                 |
| `minCoalitionSize`         | `2`             | Members needed for a coalition to become active                                        |
| `quorum`                   | `0.5`           | Share (≤ 1) or count (> 1) of negotiating agents an agreement must include             |
| `stagnationThreshold`      | `0.05`          | Convergence delta below which a round counts as no progress                            |
| `maxRoundsWithoutProgress` | `3`             | Rounds without progress before mediation / deadlock                                    |
| `approvalGates`            | `[]`            | Approval gates: `agreement-reached`, `high-value-term`, `coalition-formed`, `deadlock` |

`onDeadlock` values:

| Value                  | Outcome                                                                                                                                                         |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'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.

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

| Scenario                                | Strategy       |
| --------------------------------------- | -------------- |
| Team of specialists with a coordinator  | `hierarchical` |
| Distribute load across identical agents | `round-robin`  |
| Multiple reviewers must agree           | `consensus`    |
| Route to the best-suited expert         | `auction`      |
| Step-by-step content or data processing | `pipeline`     |
| Evaluate pros and cons of a decision    | `debate`       |
| Agents must reach a binding agreement   | `negotiation`  |
