# Agent Communication

URL: https://cogitator.app/docs/swarms/communication

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

## Overview

Agents in a swarm communicate through three complementary primitives:

| Primitive        | Purpose                                                  | Pattern                       |
| ---------------- | -------------------------------------------------------- | ----------------------------- |
| **Blackboard**   | Shared key-value state that any agent can read/write     | Publish-subscribe on sections |
| **MessageBus**   | Direct agent-to-agent or broadcast messaging             | Point-to-point or broadcast   |
| **EventEmitter** | Coordination signals emitted by the swarm and strategies | Observer 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](/docs/swarms/distributed). 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

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

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

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

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

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

### Sending Messages

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

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

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

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

| Event              | Data                                       |
| ------------------ | ------------------------------------------ |
| `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:

| Event                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | Strategy    |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- |
| `round-robin:assigned`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | Round-robin |
| `consensus:round`, `consensus:turn`, `consensus:vote`, `consensus:reached`                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | Consensus   |
| `auction:start`, `auction:bid`, `auction:winner`, `auction:complete`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | Auction     |
| `pipeline:stage`, `pipeline:stage:complete`, `pipeline:gate:pass`, `pipeline:gate:fail`                                                                                                                                                                                                                                                                                                                                                                                                                                                                | Pipeline    |
| `debate:round`, `debate:turn`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | Debate      |
| `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:complete`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Assessor    |

### Querying Event History

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

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

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

| Factory                  | Tools                                                                                                                                                                     |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `createMessagingTools`   | `send_message` (optional `waitForReply`), `read_messages` (marks returned messages read), `broadcast_message`, `reply_to_message`                                         |
| `createBlackboardTools`  | `read_blackboard`, `write_blackboard` (`merge` extends arrays / merges objects), `append_blackboard`, `list_blackboard_sections`, `get_blackboard_history`                |
| `createDelegationTools`  | `delegate_task`, `check_progress`, `request_revision`, `list_workers`                                                                                                     |
| `createVotingTools`      | `cast_vote`, `get_votes`, `change_vote`, `get_consensus_status`                                                                                                           |
| `createNegotiationTools` | `make_offer`, `counter_offer`, `accept_offer`, `reject_offer`, `get_negotiation_status`, `get_current_offers`, `propose_coalition`, `join_coalition`, `declare_interests` |

```typescript
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](/docs/swarms/distributed) for details.
