Cogitator
Swarms

Agent Communication

Blackboard shared state, MessageBus direct messaging, and EventEmitter coordination for inter-agent communication.

Overview

Agents in a swarm communicate through three complementary primitives:

PrimitivePurposePattern
BlackboardShared key-value state that any agent can read/writePublish-subscribe on sections
MessageBusDirect agent-to-agent or broadcast messagingPoint-to-point or broadcast
EventEmitterCoordination signals emitted by the swarm and strategiesObserver pattern

Every Swarm instance exposes all three via swarm.blackboard, swarm.messageBus, and swarm.events. The SwarmCoordinator creates them from your configuration — in-memory by default, Redis-backed in distributed mode. Strategies use the blackboard for their own state (sections such as consensus, auction, pipeline, debate), so you can watch a run from outside.

Blackboard

The blackboard is a shared state store organized into named sections. Agents read and write to sections, enabling asynchronous collaboration without direct messaging.

Configuration

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

const team = swarm('research-team')
  .strategy('hierarchical')
  .supervisor(leadAgent)
  .workers([searcherAgent, analyzerAgent])
  .blackboardConfig({
    enabled: true,
    sections: {
      sources: [],
      facts: [],
      conclusions: [],
    },
    trackHistory: true,
  })
  .build(cogitator);

API

const bb = team.blackboard;

bb.write('sources', ['https://example.com/paper.pdf'], 'searcher');

const sources = bb.read<string[]>('sources'); // throws if the section does not exist

bb.append('facts', { claim: 'X is true', source: 'paper.pdf' }, 'analyzer');

bb.has('conclusions');

bb.getSections();

const section = bb.getSection<string[]>('sources');
// { name, data, lastModified, modifiedBy, version }

bb.delete('conclusions');

History Tracking

When trackHistory: true, every write is recorded with the author, timestamp, and version number:

const history = bb.getHistory('facts');
// [
//   { value: [...], writtenBy: 'analyzer', timestamp: 1700000000, version: 1 },
//   { value: [...], writtenBy: 'analyzer', timestamp: 1700000005, version: 2 },
// ]

Subscriptions

React to changes in real-time:

const unsubscribe = bb.subscribe('conclusions', (data, agentName) => {
  console.log(`${agentName} updated conclusions:`, data);
});

// later
unsubscribe();

MessageBus

The message bus enables direct communication between agents. Messages are typed, routable, and support both point-to-point and broadcast patterns.

Configuration

const team = swarm('collab-team')
  .strategy('consensus')
  .agents([agentA, agentB, agentC])
  .messaging({
    enabled: true,
    maxMessageLength: 2000,
    maxMessagesPerTurn: 5,
    maxTotalMessages: 100,
  })
  .build(cogitator);

Sending Messages

const bus = team.messageBus;

await bus.send({
  swarmId: team.id,
  from: 'agent-a',
  to: 'agent-b',
  type: 'request',
  content: 'Can you review my analysis?',
  channel: 'reviews',
});

await bus.broadcast('agent-a', 'I found something important', 'announcements');

send resolves to the stored message (with id and timestamp) and throws when messaging is disabled or a limit below is hit.

Querying Messages

const myMessages = bus.getMessages('agent-b', 20); // last 20 sent by, to or broadcast for agent-b
const unread = bus.getUnreadMessages('agent-b');
const thread = bus.getConversation('agent-a', 'agent-b');
const all = bus.getAllMessages();

Read tracking is per agent. Inside a swarm, unread messages are injected into the recipient's next turn exactly once (emitting message:received), and the read_messages tool marks the messages it returns as read. swarm.messageBus is typed as ReadTrackingMessageBus, so it also has markAsRead(agentName, ids) and onMessage(listener) (every message sent on the bus; returns an unsubscribe function).

Subscribing to Incoming Messages

const unsubscribe = bus.subscribe('agent-b', (message) => {
  console.log(`${message.from}: ${message.content}`);
});

Rate Limiting

The InMemoryMessageBus enforces the limits you set in the configuration:

  • maxMessageLength — rejects messages that exceed the character limit
  • maxMessagesPerTurn — per-agent message cap per turn; the coordinator resets it at the start of every agent turn (bus.resetTurnCounts(agentName))
  • maxTotalMessages — hard cap on total messages to prevent infinite loops

EventEmitter

The swarm event system tracks all coordination activity. Strategies emit events at key points, and you can subscribe from outside the swarm for logging, metrics, or UI updates.

Subscribing

team.on('agent:start', (event) => {
  console.log(`[${event.type}] ${event.agentName} at ${event.timestamp}`);
});

team.on('swarm:complete', (event) => {
  console.log('Swarm finished:', event.data);
});

// wildcard catches everything
team.on('*', (event) => {
  metrics.record(event.type, event.data);
});

// one-time listener
team.once('consensus:reached', (event) => {
  const { decision } = event.data as { decision: string };
  console.log(`Decision: ${decision}`);
});

Built-in Event Types

Events emitted by the swarm and its coordinator (event.data holds the payload):

EventData
swarm:start{ swarmId, strategy, input }
swarm:complete{ swarmId, outputLength, agentCount }
swarm:error{ swarmId, error }
swarm:paused{ swarmId }
swarm:resumed{ swarmId }
swarm:aborted{ swarmId }
swarm:reset{ swarmId }
agent:start{ agentName, input }
agent:complete{ agentName, result }
agent:error{ agentName, error }
message:sent{ id, from, to, type, channel, content }
message:received{ agentName, count, messageIds }
blackboard:write{ section, version, writtenBy, deleted }

Strategy-specific events:

EventStrategy
round-robin:assignedRound-robin
consensus:round, consensus:turn, consensus:vote, consensus:reachedConsensus
auction:start, auction:bid, auction:winner, auction:completeAuction
pipeline:stage, pipeline:stage:complete, pipeline:gate:pass, pipeline:gate:failPipeline
debate:round, debate:turnDebate
negotiation:start, negotiation:round, negotiation:turn, negotiation:phase-change, negotiation:offer-made, negotiation:offer-accepted, negotiation:offer-rejected, negotiation:offer-countered, negotiation:convergence-update, negotiation:stagnation-detected, negotiation:mediation-suggested, negotiation:approval-required, negotiation:approval-received, negotiation:agreement-reached, negotiation:deadlock, negotiation:escalation, negotiation:arbitration, negotiation:terminated (and coalition events)Negotiation
assessor:completeAssessor

Querying Event History

The event emitter keeps a rolling buffer of recent events (default 1000):

const allEvents = team.events.getEvents();
const agentEvents = team.events.getEventsByAgent('frontend-dev');
const roundEvents = team.events.getEventsByType('consensus:round');
team.events.clearEvents();

swarm.events is typed as QueryableSwarmEventEmitter, which adds getEventsByType, getEventsByAgent and clearEvents to SwarmEventEmitter. SwarmRunOptions.onEvent receives every event of one run.

Swarm Tools

Strategies attach the tools they depend on: the hierarchical supervisor gets the delegation tools, consensus voters the voting tools and negotiating agents the negotiation tools. The messaging and blackboard tools are opt-in with agentTools (or SwarmBuilder.agentTools()), which gives every agent both sets bound to the swarm's own bus and blackboard:

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

Tools an agent already defines with the same name are left untouched. All of this applies to in-process swarms only: distributed swarms attach no tools, and agentTools is rejected there (and when messaging.enabled or blackboard.enabled is false).

The factories build the same tools for a given blackboard, bus and event emitter:

FactoryTools
createMessagingToolssend_message (optional waitForReply), read_messages (marks returned messages read), broadcast_message, reply_to_message
createBlackboardToolsread_blackboard, write_blackboard (merge extends arrays / merges objects), append_blackboard, list_blackboard_sections, get_blackboard_history
createDelegationToolsdelegate_task, check_progress, request_revision, list_workers
createVotingToolscast_vote, get_votes, change_vote, get_consensus_status
createNegotiationToolsmake_offer, counter_offer, accept_offer, reject_offer, get_negotiation_status, get_current_offers, propose_coalition, join_coalition, declare_interests
import { createBlackboardTools, createVotingTools } from '@cogitator-ai/swarms';

const { readBlackboard, writeBlackboard } = createBlackboardTools(team.blackboard, 'analyst');
const voting = createVotingTools(team.blackboard, team.events, 'analyst', 2);

Each factory returns an object of tools bound to one agent name. Because the blackboard, bus and events belong to a built swarm while an Agent's tools are fixed when it is constructed, these factories are mainly for custom strategies and coordinators: createSwarmTools(context) returns every tool and createStrategyTools(strategy, context) the set a strategy needs, from a SwarmToolContext (coordinator, blackboard, messageBus, events, agentName, optional agentWeight and swarmId). Messaging tools created that way enforce the hierarchical communication policy (createHierarchyMessageAuthorizer).

Redis-Backed Variants

For distributed swarms, all three communication primitives have Redis-backed implementations that synchronize state across machines:

  • RedisMessageBus — messages persisted in Redis lists, delivered via pub/sub
  • RedisBlackboard — sections stored in Redis keys, changes broadcast via pub/sub
  • RedisSwarmEventEmitter — events stored in Redis lists, live-streamed via pub/sub

These are used automatically when you enable distributed mode. See Distributed Swarms for details.

On this page