Cogitator
Swarms

Distributed Swarms

Scale swarms across multiple machines with Redis-backed coordination, distributed worker nodes, and shared state.

Overview

By default, swarms run in-memory on a single process. For production workloads, you can enable distributed mode to coordinate agents across multiple machines using Redis as the communication backbone.

┌─────────────────────────┐
│     Swarm Coordinator   │
│     (orchestrator node)  │
│                         │
│  Strategy ──▶ Redis     │
│             (pub/sub)   │
└──────────┬──────────────┘
           │ job queue
    ┌──────┴──────┐
    ▼             ▼
┌─────────┐  ┌─────────┐
│ Worker  │  │ Worker  │
│ Node 1  │  │ Node 2  │
│         │  │         │
│ Cogitator│  │ Cogitator│
│ + Agent │  │ + Agent │
└─────────┘  └─────────┘

The orchestrator node runs the strategy and dispatches agent jobs to a Redis queue. Worker nodes pick up jobs, execute the agent, and publish results back through Redis.

Enabling Distributed Mode

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

const model = 'ollama/llama3.2';

const team = swarm('distributed-team')
  .strategy('pipeline')
  .pipeline({
    stages: [
      {
        name: 'draft',
        agent: new Agent({ name: 'writer', model, instructions: 'Draft the text.' }),
      },
      { name: 'review', agent: new Agent({ name: 'editor', model, instructions: 'Review it.' }) },
    ],
  })
  .distributed({
    enabled: true,
    redis: {
      host: 'redis.example.com',
      port: 6379,
      password: process.env.REDIS_PASSWORD,
      db: 0,
      keyPrefix: 'swarm',
    },
    queue: 'swarm-agent-jobs',
    timeout: 300_000,
  })
  .build(cogitator);

await team.run({ input: 'Write the release notes' });
await team.close();

Always call team.close() when done: it closes the Redis connections and schedules the swarm's Redis state to expire after cleanupAfter.

Distributed Configuration

OptionDefaultDescription
enabledfalseEnable distributed mode
redis.host'localhost'Redis server hostname
redis.port6379Redis server port
redis.passwordundefinedRedis authentication password
redis.db0Redis database number
redis.keyPrefix'swarm'Key prefix for all Redis keys
queue'swarm-agent-jobs'Job queue name ({keyPrefix}:jobs:{queue})
timeout300000Timeout in ms for waiting on agent job results
retryundefined (no retries)Re-dispatch jobs that fail on a worker or time out: maxRetries (3), backoff ('exponential'; or 'constant', 'linear'), initialDelay (1000 ms), maxDelay (30000 ms)
cleanupAfter3600000How long the swarm's Redis state (blackboard, messages, events) is kept after close(), in ms; 0 deletes it at once

retry acts before the swarm's errorHandling sees the failure, and an aborted run is never retried. A timed-out job may still be running on its worker, so a retried turn can run twice. workerConcurrency is deprecated and has no effect: set concurrency where the workers run, with DistributedSwarmWorker's concurrency option.

swarm('team')
  .strategy('pipeline')
  .pipeline({ stages })
  .distributed({
    enabled: true,
    redis: { host: 'localhost', port: 6379 },
    retry: { maxRetries: 2, backoff: 'exponential', initialDelay: 500 },
    cleanupAfter: 600_000,
  });

How It Works

When distributed mode is enabled, the Swarm creates a DistributedSwarmCoordinator instead of the local SwarmCoordinator. The distributed coordinator:

  1. Uses Redis-backed primitives — RedisMessageBus, RedisBlackboard, and RedisSwarmEventEmitter replace their in-memory counterparts. They are available right after construction and connect lazily on the first run.
  2. Dispatches jobs — when a strategy runs an agent, the coordinator serializes the agent (model, instructions, limits and tool schemas), the full turn context and run options, and pushes a SwarmAgentJobPayload onto the Redis list {keyPrefix}:jobs:{queue} (see swarmJobQueueKey()).
  3. Waits for results — results arrive on the {keyPrefix}:{swarmId}:results pub/sub channel and are matched by job id. Turns time out after timeout ms and are cancelled when the swarm is aborted or the run times out.
  4. Applies the same policies as local swarms — retry, failover, skip, budgets, circuit breaking and events behave exactly like in-process execution.
  5. Synchronizes state — blackboard writes and messages are persisted in Redis, so all nodes see the same state.

The worker runs each turn on the model the agent would use in-process. The payload carries the agent's model (or the swarm's resolveModel default), prefixed with the agent's own provider when it sets one, and the worker routes it with its own cogitator: a model like openrouter/deepseek/deepseek-v4-pro runs on the worker's openrouter backend. Give the worker the same llm.backends, plugins and provider keys as the process that runs the swarm. A provider it cannot route to fails the turn.

Strategy tools that call back into the coordinator (hierarchical delegation, voting, negotiation tools) only exist in-process: a distributed hierarchical supervisor cannot delegate, distributed negotiators cannot make offers, and consensus voters vote with a VOTE: line. agentTools is rejected for distributed swarms. Prefer pipeline, round-robin, consensus, debate and auction for distributed swarms. Agent tools are executed on the worker from its own tool registry.

Redis Key Structure

All keys are prefixed with {keyPrefix}:{swarmId}:

Key PatternTypePurpose
swarm:{id}:messagesListMessage bus message history
swarm:{id}:channel:{target}Pub/SubReal-time message delivery
swarm:{id}:blackboard:{section}String (JSON)Blackboard section data
swarm:{id}:blackboard:{section}:historyListBlackboard write history
swarm:{id}:blackboard:changesPub/SubBlackboard change notifications
swarm:{id}:eventsListEvent history
swarm:{id}:events:livePub/SubLive event streaming
swarm:{id}:resultsPub/SubAgent job results
swarm:jobs:{queue}ListJob queue for worker nodes

RedisMessageBus

Drop-in replacement for InMemoryMessageBus. Messages are stored in a Redis list and delivered in real-time via pub/sub pattern subscriptions.

import { RedisMessageBus } from '@cogitator-ai/swarms';
import Redis from 'ioredis';

const bus = new RedisMessageBus(
  { enabled: true },
  { redis: new Redis(), swarmId: 'swarm_abc123', keyPrefix: 'swarm' }
);

await bus.initialize();

await bus.send({
  swarmId: 'swarm_abc123',
  from: 'agent-a',
  to: 'agent-b',
  type: 'request',
  content: 'Need your analysis',
});

await bus.syncFromRedis();

await bus.close();

The bus maintains a local cache for fast reads and subscribes to Redis pub/sub channels using pattern matching (swarm:{id}:channel:*) for real-time delivery.

RedisBlackboard

Shared state synchronized across all nodes. Each section is stored as a JSON string in Redis, with change notifications broadcast via pub/sub.

import { RedisBlackboard } from '@cogitator-ai/swarms';
import Redis from 'ioredis';

const bb = new RedisBlackboard(
  { enabled: true, sections: { tasks: [] }, trackHistory: true },
  { redis: new Redis(), swarmId: 'swarm_abc123', keyPrefix: 'swarm' }
);

await bb.initialize();

bb.write('tasks', [{ id: 1, title: 'Implement auth' }], 'lead');

const tasks = bb.read('tasks');

bb.subscribe('tasks', (data, agentName) => {
  console.log(`Tasks updated by ${agentName}`);
});

await bb.syncFromRedis();
await bb.close();

On initialization, existing sections are loaded from Redis. The local cache provides instant reads, while writes are persisted to Redis and broadcast to all subscribers.

RedisSwarmEventEmitter

Events are persisted in a Redis list and streamed live to all connected nodes via pub/sub.

import { RedisSwarmEventEmitter } from '@cogitator-ai/swarms';
import Redis from 'ioredis';

const events = new RedisSwarmEventEmitter({
  redis: new Redis(),
  swarmId: 'swarm_abc123',
  keyPrefix: 'swarm',
  maxEvents: 1000,
});

await events.initialize();

events.on('agent:complete', (event) => {
  console.log(`Agent done: ${event.agentName}`);
});

await events.emitAsync('agent:start', { agentName: 'worker-a' });

const history = await events.getEventsAsync();

await events.close();

The emitter auto-trims the event list in Redis to maxEvents entries to prevent unbounded growth.

Running Worker Nodes

DistributedSwarmWorker from @cogitator-ai/worker consumes the job queue, runs each agent turn with its own Cogitator and tools, and publishes the result back to the coordinator:

import { Cogitator } from '@cogitator-ai/core';
import { DistributedSwarmWorker } from '@cogitator-ai/worker';

const worker = new DistributedSwarmWorker(
  {
    redis: { host: 'redis.example.com', port: 6379, password: process.env.REDIS_PASSWORD },
    keyPrefix: 'swarm', // must match distributed.redis.keyPrefix
    queue: 'swarm-agent-jobs', // must match distributed.queue
    concurrency: 4,
    cogitator: new Cogitator({ llm: { defaultModel: 'ollama/llama3.2' } }),
    tools: [searchTool, calculatorTool],
  },
  {
    onJobFailed: (job, error) => console.error(job.agentName, error.message),
  }
);

await worker.start();
process.on('SIGTERM', () => void worker.stop());

Agents reference tools by name; a turn that needs a tool missing from the worker's tools fails with a clear error instead of running with a fake implementation. stop() stops taking new jobs and waits for in-flight turns.

Scaling Considerations

Horizontal scaling — spin up multiple worker nodes consuming from the same job queue. Redis BLPOP ensures each job is processed by exactly one worker; concurrency controls parallel turns per node.

State consistency — the blackboard and message bus use Redis as the source of truth. Local caches provide fast reads; writes are immediately persisted and broadcast.

Connection management — a distributed swarm uses a command connection plus subscriber connections for messages, blackboard, events and results. Call close() when done to release them.

Timeouts — configure the timeout option based on your expected agent execution time. Jobs that exceed the timeout will cause the coordinator to throw an error.

Key cleanup — swarm keys persist in Redis after execution. For production use, set a TTL on keys or implement periodic cleanup based on the swarmId.

On this page