@cogitator-ai/swarms 0.3.19 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/dist/communication/index.d.ts +6 -3
- package/dist/communication/index.d.ts.map +1 -1
- package/dist/communication/index.js +6 -3
- package/dist/communication/index.js.map +1 -1
- package/dist/communication/redis-blackboard.d.ts +36 -0
- package/dist/communication/redis-blackboard.d.ts.map +1 -0
- package/dist/communication/redis-blackboard.js +203 -0
- package/dist/communication/redis-blackboard.js.map +1 -0
- package/dist/communication/redis-event-emitter.d.ts +36 -0
- package/dist/communication/redis-event-emitter.d.ts.map +1 -0
- package/dist/communication/redis-event-emitter.js +130 -0
- package/dist/communication/redis-event-emitter.js.map +1 -0
- package/dist/communication/redis-message-bus.d.ts +34 -0
- package/dist/communication/redis-message-bus.d.ts.map +1 -0
- package/dist/communication/redis-message-bus.js +151 -0
- package/dist/communication/redis-message-bus.js.map +1 -0
- package/dist/distributed/distributed-coordinator.d.ts +92 -0
- package/dist/distributed/distributed-coordinator.d.ts.map +1 -0
- package/dist/distributed/distributed-coordinator.js +329 -0
- package/dist/distributed/distributed-coordinator.js.map +1 -0
- package/dist/distributed/index.d.ts +2 -0
- package/dist/distributed/index.d.ts.map +1 -0
- package/dist/distributed/index.js +2 -0
- package/dist/distributed/index.js.map +1 -0
- package/dist/strategies/auction.d.ts +3 -4
- package/dist/strategies/auction.d.ts.map +1 -1
- package/dist/strategies/auction.js +1 -1
- package/dist/strategies/auction.js.map +1 -1
- package/dist/strategies/consensus.d.ts +3 -4
- package/dist/strategies/consensus.d.ts.map +1 -1
- package/dist/strategies/consensus.js +1 -1
- package/dist/strategies/consensus.js.map +1 -1
- package/dist/strategies/debate.d.ts +3 -4
- package/dist/strategies/debate.d.ts.map +1 -1
- package/dist/strategies/debate.js +1 -1
- package/dist/strategies/debate.js.map +1 -1
- package/dist/strategies/hierarchical.d.ts +3 -4
- package/dist/strategies/hierarchical.d.ts.map +1 -1
- package/dist/strategies/hierarchical.js +1 -1
- package/dist/strategies/hierarchical.js.map +1 -1
- package/dist/strategies/index.d.ts +2 -3
- package/dist/strategies/index.d.ts.map +1 -1
- package/dist/strategies/index.js.map +1 -1
- package/dist/strategies/negotiation-strategy.d.ts +3 -4
- package/dist/strategies/negotiation-strategy.d.ts.map +1 -1
- package/dist/strategies/negotiation-strategy.js +4 -4
- package/dist/strategies/negotiation-strategy.js.map +1 -1
- package/dist/strategies/pipeline.d.ts +3 -4
- package/dist/strategies/pipeline.d.ts.map +1 -1
- package/dist/strategies/pipeline.js +1 -1
- package/dist/strategies/pipeline.js.map +1 -1
- package/dist/strategies/round-robin.d.ts +3 -4
- package/dist/strategies/round-robin.d.ts.map +1 -1
- package/dist/strategies/round-robin.js +1 -1
- package/dist/strategies/round-robin.js.map +1 -1
- package/dist/swarm.d.ts +14 -1
- package/dist/swarm.d.ts.map +1 -1
- package/dist/swarm.js +101 -15
- package/dist/swarm.js.map +1 -1
- package/package.json +5 -4
- package/dist/strategies/negotiation.d.ts +0 -39
- package/dist/strategies/negotiation.d.ts.map +0 -1
- package/dist/strategies/negotiation.js +0 -607
- package/dist/strategies/negotiation.js.map +0 -1
package/README.md
CHANGED
|
@@ -655,6 +655,185 @@ swarm.once('swarm:complete', (event) => {
|
|
|
655
655
|
|
|
656
656
|
---
|
|
657
657
|
|
|
658
|
+
## Distributed Execution
|
|
659
|
+
|
|
660
|
+
Run swarm agents across multiple workers using Redis-backed communication and BullMQ job queues. Each agent executes as a separate job, enabling horizontal scaling and parallel processing.
|
|
661
|
+
|
|
662
|
+
### Basic Distributed Swarm
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
import { Cogitator, Agent } from '@cogitator-ai/core';
|
|
666
|
+
import { SwarmBuilder } from '@cogitator-ai/swarms';
|
|
667
|
+
|
|
668
|
+
const cogitator = new Cogitator({ defaultModel: 'gpt-4o' });
|
|
669
|
+
|
|
670
|
+
const swarm = new SwarmBuilder('distributed-team')
|
|
671
|
+
.strategy('hierarchical')
|
|
672
|
+
.supervisor(new Agent({ name: 'lead', instructions: 'Coordinate the team' }))
|
|
673
|
+
.workers([
|
|
674
|
+
new Agent({ name: 'analyst-1', instructions: 'Analyze data' }),
|
|
675
|
+
new Agent({ name: 'analyst-2', instructions: 'Analyze data' }),
|
|
676
|
+
new Agent({ name: 'analyst-3', instructions: 'Analyze data' }),
|
|
677
|
+
])
|
|
678
|
+
.distributed({
|
|
679
|
+
enabled: true,
|
|
680
|
+
queue: 'swarm-agent-jobs',
|
|
681
|
+
timeout: 300000,
|
|
682
|
+
redis: {
|
|
683
|
+
host: 'localhost',
|
|
684
|
+
port: 6379,
|
|
685
|
+
},
|
|
686
|
+
})
|
|
687
|
+
.build(cogitator);
|
|
688
|
+
|
|
689
|
+
const result = await swarm.run({
|
|
690
|
+
input: 'Analyze Q4 sales data across all regions',
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// Cleanup Redis connections when done
|
|
694
|
+
await swarm.close();
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Distributed Configuration Options
|
|
698
|
+
|
|
699
|
+
```typescript
|
|
700
|
+
interface DistributedSwarmConfig {
|
|
701
|
+
enabled: boolean;
|
|
702
|
+
queue?: string; // Job queue name (default: 'swarm-agent-jobs')
|
|
703
|
+
workerConcurrency?: number; // Workers per process (default: 4)
|
|
704
|
+
timeout?: number; // Job timeout in ms (default: 300000)
|
|
705
|
+
redis?: {
|
|
706
|
+
host?: string; // Redis host (default: 'localhost')
|
|
707
|
+
port?: number; // Redis port (default: 6379)
|
|
708
|
+
password?: string; // Redis password
|
|
709
|
+
keyPrefix?: string; // Key prefix (default: 'swarm')
|
|
710
|
+
db?: number; // Redis database (default: 0)
|
|
711
|
+
};
|
|
712
|
+
retry?: {
|
|
713
|
+
maxRetries?: number; // Max retry attempts
|
|
714
|
+
backoff?: 'constant' | 'linear' | 'exponential';
|
|
715
|
+
initialDelay?: number; // Initial delay in ms
|
|
716
|
+
maxDelay?: number; // Max delay in ms
|
|
717
|
+
};
|
|
718
|
+
cleanupAfter?: number; // Cleanup keys after ms
|
|
719
|
+
}
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### Redis-Backed Communication
|
|
723
|
+
|
|
724
|
+
Distributed swarms use Redis for shared state synchronization:
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
import { RedisMessageBus, RedisBlackboard, RedisSwarmEventEmitter } from '@cogitator-ai/swarms';
|
|
728
|
+
import Redis from 'ioredis';
|
|
729
|
+
|
|
730
|
+
const redis = new Redis({ host: 'localhost', port: 6379 });
|
|
731
|
+
|
|
732
|
+
// Message bus for agent-to-agent communication
|
|
733
|
+
const messageBus = new RedisMessageBus(
|
|
734
|
+
{ enabled: true, protocol: 'direct' },
|
|
735
|
+
{ redis, swarmId: 'my-swarm', keyPrefix: 'swarm' }
|
|
736
|
+
);
|
|
737
|
+
await messageBus.initialize();
|
|
738
|
+
|
|
739
|
+
// Shared blackboard for state
|
|
740
|
+
const blackboard = new RedisBlackboard(
|
|
741
|
+
{ enabled: true, sections: { results: [] }, trackHistory: true },
|
|
742
|
+
{ redis, swarmId: 'my-swarm', keyPrefix: 'swarm' }
|
|
743
|
+
);
|
|
744
|
+
await blackboard.initialize();
|
|
745
|
+
|
|
746
|
+
// Event emitter for cross-worker events
|
|
747
|
+
const events = new RedisSwarmEventEmitter({
|
|
748
|
+
redis,
|
|
749
|
+
swarmId: 'my-swarm',
|
|
750
|
+
keyPrefix: 'swarm',
|
|
751
|
+
});
|
|
752
|
+
await events.initialize();
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Setting Up Workers
|
|
756
|
+
|
|
757
|
+
Workers process distributed swarm jobs. Use with `@cogitator-ai/worker`:
|
|
758
|
+
|
|
759
|
+
```typescript
|
|
760
|
+
import { WorkerPool } from '@cogitator-ai/worker';
|
|
761
|
+
|
|
762
|
+
const pool = new WorkerPool({
|
|
763
|
+
concurrency: 4,
|
|
764
|
+
redis: {
|
|
765
|
+
host: 'localhost',
|
|
766
|
+
port: 6379,
|
|
767
|
+
},
|
|
768
|
+
queues: ['swarm-agent-jobs'],
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
await pool.start();
|
|
772
|
+
|
|
773
|
+
// Workers automatically process swarm-agent jobs
|
|
774
|
+
// Each job runs a single agent and publishes results back to Redis
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### Architecture
|
|
778
|
+
|
|
779
|
+
```
|
|
780
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
781
|
+
│ Swarm.run() │
|
|
782
|
+
│ distributed: true → DistributedSwarmCoordinator │
|
|
783
|
+
│ distributed: false → SwarmCoordinator (in-memory) │
|
|
784
|
+
└─────────────────────────────────────────────────────────────┘
|
|
785
|
+
│
|
|
786
|
+
┌───────────────┴───────────────┐
|
|
787
|
+
▼ ▼
|
|
788
|
+
┌─────────────────────────┐ ┌─────────────────────────────┐
|
|
789
|
+
│ DistributedCoordinator │ │ Redis (Shared State) │
|
|
790
|
+
│ - dispatches agent jobs│────▶│ - swarm:{id}:blackboard │
|
|
791
|
+
│ - subscribes to results│ │ - swarm:{id}:messages │
|
|
792
|
+
│ - coordinates strategy │ │ - swarm:{id}:results │
|
|
793
|
+
└─────────────────────────┘ └─────────────────────────────┘
|
|
794
|
+
│ ▲
|
|
795
|
+
│ job queue │
|
|
796
|
+
▼ │
|
|
797
|
+
┌─────────────────────────┐ │
|
|
798
|
+
│ BullMQ Queue │ │
|
|
799
|
+
│ swarm-agent-jobs │ │
|
|
800
|
+
└─────────────────────────┘ │
|
|
801
|
+
│ │
|
|
802
|
+
┌─────────┼─────────┐ │
|
|
803
|
+
▼ ▼ ▼ │
|
|
804
|
+
┌───────┐ ┌───────┐ ┌───────┐ │
|
|
805
|
+
│Worker1│ │Worker2│ │Worker3│ ───────────────┘
|
|
806
|
+
│ Agent │ │ Agent │ │ Agent │ publish results
|
|
807
|
+
└───────┘ └───────┘ └───────┘
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### Local vs Distributed
|
|
811
|
+
|
|
812
|
+
The same swarm works in both modes with identical API:
|
|
813
|
+
|
|
814
|
+
```typescript
|
|
815
|
+
// Local execution (in-process)
|
|
816
|
+
const localSwarm = new SwarmBuilder('local-team')
|
|
817
|
+
.strategy('consensus')
|
|
818
|
+
.agents([agent1, agent2, agent3])
|
|
819
|
+
.consensus({ threshold: 0.6, maxRounds: 3, resolution: 'majority', onNoConsensus: 'fail' })
|
|
820
|
+
.build(cogitator);
|
|
821
|
+
|
|
822
|
+
// Distributed execution (across workers)
|
|
823
|
+
const distributedSwarm = new SwarmBuilder('distributed-team')
|
|
824
|
+
.strategy('consensus')
|
|
825
|
+
.agents([agent1, agent2, agent3])
|
|
826
|
+
.consensus({ threshold: 0.6, maxRounds: 3, resolution: 'majority', onNoConsensus: 'fail' })
|
|
827
|
+
.distributed({ enabled: true, redis: { host: 'redis.example.com' } })
|
|
828
|
+
.build(cogitator);
|
|
829
|
+
|
|
830
|
+
// Same API for both
|
|
831
|
+
const localResult = await localSwarm.run({ input: 'Task...' });
|
|
832
|
+
const distributedResult = await distributedSwarm.run({ input: 'Task...' });
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
---
|
|
836
|
+
|
|
658
837
|
## Swarm Control
|
|
659
838
|
|
|
660
839
|
### Pause and Resume
|
|
@@ -784,6 +963,19 @@ import type {
|
|
|
784
963
|
} from '@cogitator-ai/swarms';
|
|
785
964
|
```
|
|
786
965
|
|
|
966
|
+
### Distributed Types
|
|
967
|
+
|
|
968
|
+
```typescript
|
|
969
|
+
import type { DistributedSwarmConfig } from '@cogitator-ai/types';
|
|
970
|
+
|
|
971
|
+
import {
|
|
972
|
+
RedisMessageBus,
|
|
973
|
+
RedisBlackboard,
|
|
974
|
+
RedisSwarmEventEmitter,
|
|
975
|
+
DistributedSwarmCoordinator,
|
|
976
|
+
} from '@cogitator-ai/swarms';
|
|
977
|
+
```
|
|
978
|
+
|
|
787
979
|
---
|
|
788
980
|
|
|
789
981
|
## Examples
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Communication primitives for swarm coordination
|
|
3
3
|
*/
|
|
4
|
-
export { SwarmEventEmitterImpl } from './event-emitter';
|
|
5
|
-
export { InMemoryMessageBus, createMessageBus } from './message-bus';
|
|
6
|
-
export { InMemoryBlackboard, createBlackboard } from './blackboard';
|
|
4
|
+
export { SwarmEventEmitterImpl } from './event-emitter.js';
|
|
5
|
+
export { InMemoryMessageBus, createMessageBus } from './message-bus.js';
|
|
6
|
+
export { InMemoryBlackboard, createBlackboard } from './blackboard.js';
|
|
7
|
+
export { RedisMessageBus, type RedisMessageBusOptions } from './redis-message-bus.js';
|
|
8
|
+
export { RedisBlackboard, type RedisBlackboardOptions } from './redis-blackboard.js';
|
|
9
|
+
export { RedisSwarmEventEmitter, type RedisEventEmitterOptions } from './redis-event-emitter.js';
|
|
7
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/communication/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/communication/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACrF,OAAO,EAAE,sBAAsB,EAAE,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Communication primitives for swarm coordination
|
|
3
3
|
*/
|
|
4
|
-
export { SwarmEventEmitterImpl } from './event-emitter';
|
|
5
|
-
export { InMemoryMessageBus, createMessageBus } from './message-bus';
|
|
6
|
-
export { InMemoryBlackboard, createBlackboard } from './blackboard';
|
|
4
|
+
export { SwarmEventEmitterImpl } from './event-emitter.js';
|
|
5
|
+
export { InMemoryMessageBus, createMessageBus } from './message-bus.js';
|
|
6
|
+
export { InMemoryBlackboard, createBlackboard } from './blackboard.js';
|
|
7
|
+
export { RedisMessageBus } from './redis-message-bus.js';
|
|
8
|
+
export { RedisBlackboard } from './redis-blackboard.js';
|
|
9
|
+
export { RedisSwarmEventEmitter } from './redis-event-emitter.js';
|
|
7
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/communication/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/communication/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,eAAe,EAA+B,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,eAAe,EAA+B,MAAM,uBAAuB,CAAC;AACrF,OAAO,EAAE,sBAAsB,EAAiC,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Blackboard, BlackboardConfig, BlackboardSection, BlackboardHistoryEntry } from '@cogitator-ai/types';
|
|
2
|
+
import type { Redis } from 'ioredis';
|
|
3
|
+
export interface RedisBlackboardOptions {
|
|
4
|
+
redis: Redis;
|
|
5
|
+
swarmId: string;
|
|
6
|
+
keyPrefix?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class RedisBlackboard implements Blackboard {
|
|
9
|
+
private redis;
|
|
10
|
+
private subscriber;
|
|
11
|
+
private swarmId;
|
|
12
|
+
private keyPrefix;
|
|
13
|
+
private config;
|
|
14
|
+
private subscriptions;
|
|
15
|
+
private localCache;
|
|
16
|
+
private historyCache;
|
|
17
|
+
constructor(config: BlackboardConfig, options: RedisBlackboardOptions);
|
|
18
|
+
private sectionKey;
|
|
19
|
+
private historyKey;
|
|
20
|
+
private channelKey;
|
|
21
|
+
initialize(): Promise<void>;
|
|
22
|
+
read<T = unknown>(section: string): T;
|
|
23
|
+
write<T>(section: string, data: T, agentName: string): void;
|
|
24
|
+
append<T>(section: string, item: T, agentName: string): void;
|
|
25
|
+
has(section: string): boolean;
|
|
26
|
+
delete(section: string): void;
|
|
27
|
+
subscribe(section: string, handler: (data: unknown, agentName: string) => void): () => void;
|
|
28
|
+
getSections(): string[];
|
|
29
|
+
getSection<T = unknown>(section: string): BlackboardSection<T> | undefined;
|
|
30
|
+
getHistory(section: string): BlackboardHistoryEntry[];
|
|
31
|
+
clear(): void;
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
syncFromRedis(): Promise<void>;
|
|
34
|
+
private notifySubscribers;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=redis-blackboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-blackboard.d.ts","sourceRoot":"","sources":["../../src/communication/redis-blackboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD,qBAAa,eAAgB,YAAW,UAAU;IAChD,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,aAAa,CAAsE;IAC3F,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,YAAY,CAA+C;gBAEvD,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,sBAAsB;IAQrE,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,UAAU;IAIZ,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAyDjC,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC;IAQrC,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IA2C3D,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAgB5D,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAI7B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAQ7B,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAiB3F,WAAW,IAAI,MAAM,EAAE;IAIvB,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,SAAS;IAY1E,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,sBAAsB,EAAE;IAIrD,KAAK,IAAI,IAAI;IAWP,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAepC,OAAO,CAAC,iBAAiB;CAU1B"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
export class RedisBlackboard {
|
|
2
|
+
redis;
|
|
3
|
+
subscriber;
|
|
4
|
+
swarmId;
|
|
5
|
+
keyPrefix;
|
|
6
|
+
config;
|
|
7
|
+
subscriptions = new Map();
|
|
8
|
+
localCache = new Map();
|
|
9
|
+
historyCache = new Map();
|
|
10
|
+
constructor(config, options) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.redis = options.redis;
|
|
13
|
+
this.subscriber = options.redis.duplicate();
|
|
14
|
+
this.swarmId = options.swarmId;
|
|
15
|
+
this.keyPrefix = options.keyPrefix ?? 'swarm';
|
|
16
|
+
}
|
|
17
|
+
sectionKey(section) {
|
|
18
|
+
return `${this.keyPrefix}:${this.swarmId}:blackboard:${section}`;
|
|
19
|
+
}
|
|
20
|
+
historyKey(section) {
|
|
21
|
+
return `${this.keyPrefix}:${this.swarmId}:blackboard:${section}:history`;
|
|
22
|
+
}
|
|
23
|
+
channelKey() {
|
|
24
|
+
return `${this.keyPrefix}:${this.swarmId}:blackboard:changes`;
|
|
25
|
+
}
|
|
26
|
+
async initialize() {
|
|
27
|
+
for (const [name, initialData] of Object.entries(this.config.sections)) {
|
|
28
|
+
const section = {
|
|
29
|
+
name,
|
|
30
|
+
data: initialData,
|
|
31
|
+
lastModified: Date.now(),
|
|
32
|
+
modifiedBy: 'system',
|
|
33
|
+
version: 1,
|
|
34
|
+
};
|
|
35
|
+
this.localCache.set(name, section);
|
|
36
|
+
const existing = await this.redis.get(this.sectionKey(name));
|
|
37
|
+
if (!existing) {
|
|
38
|
+
await this.redis.set(this.sectionKey(name), JSON.stringify(section));
|
|
39
|
+
if (this.config.trackHistory) {
|
|
40
|
+
const entry = {
|
|
41
|
+
value: initialData,
|
|
42
|
+
writtenBy: 'system',
|
|
43
|
+
timestamp: Date.now(),
|
|
44
|
+
version: 1,
|
|
45
|
+
};
|
|
46
|
+
await this.redis.rpush(this.historyKey(name), JSON.stringify(entry));
|
|
47
|
+
this.historyCache.set(name, [entry]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const stored = JSON.parse(existing);
|
|
52
|
+
this.localCache.set(name, stored);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
await this.subscriber.subscribe(this.channelKey());
|
|
56
|
+
this.subscriber.on('message', (_channel, messageJson) => {
|
|
57
|
+
try {
|
|
58
|
+
const { section, data, agentName, version, timestamp } = JSON.parse(messageJson);
|
|
59
|
+
const stored = {
|
|
60
|
+
name: section,
|
|
61
|
+
data,
|
|
62
|
+
lastModified: timestamp,
|
|
63
|
+
modifiedBy: agentName,
|
|
64
|
+
version,
|
|
65
|
+
};
|
|
66
|
+
this.localCache.set(section, stored);
|
|
67
|
+
this.notifySubscribers(section, data, agentName);
|
|
68
|
+
}
|
|
69
|
+
catch { }
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
read(section) {
|
|
73
|
+
const cached = this.localCache.get(section);
|
|
74
|
+
if (!cached) {
|
|
75
|
+
throw new Error(`Blackboard section '${section}' not found`);
|
|
76
|
+
}
|
|
77
|
+
return cached.data;
|
|
78
|
+
}
|
|
79
|
+
write(section, data, agentName) {
|
|
80
|
+
if (!this.config.enabled) {
|
|
81
|
+
throw new Error('Blackboard is not enabled');
|
|
82
|
+
}
|
|
83
|
+
const existing = this.localCache.get(section);
|
|
84
|
+
const version = existing ? existing.version + 1 : 1;
|
|
85
|
+
const timestamp = Date.now();
|
|
86
|
+
const newSection = {
|
|
87
|
+
name: section,
|
|
88
|
+
data,
|
|
89
|
+
lastModified: timestamp,
|
|
90
|
+
modifiedBy: agentName,
|
|
91
|
+
version,
|
|
92
|
+
};
|
|
93
|
+
this.localCache.set(section, newSection);
|
|
94
|
+
void this.redis.set(this.sectionKey(section), JSON.stringify(newSection));
|
|
95
|
+
if (this.config.trackHistory) {
|
|
96
|
+
const entry = {
|
|
97
|
+
value: data,
|
|
98
|
+
writtenBy: agentName,
|
|
99
|
+
timestamp,
|
|
100
|
+
version,
|
|
101
|
+
};
|
|
102
|
+
if (!this.historyCache.has(section)) {
|
|
103
|
+
this.historyCache.set(section, []);
|
|
104
|
+
}
|
|
105
|
+
this.historyCache.get(section).push(entry);
|
|
106
|
+
void this.redis.rpush(this.historyKey(section), JSON.stringify(entry));
|
|
107
|
+
}
|
|
108
|
+
void this.redis.publish(this.channelKey(), JSON.stringify({ section, data, agentName, version, timestamp }));
|
|
109
|
+
this.notifySubscribers(section, data, agentName);
|
|
110
|
+
}
|
|
111
|
+
append(section, item, agentName) {
|
|
112
|
+
const current = this.localCache.get(section);
|
|
113
|
+
if (!current) {
|
|
114
|
+
this.write(section, [item], agentName);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!Array.isArray(current.data)) {
|
|
118
|
+
throw new Error(`Section '${section}' is not an array, cannot append`);
|
|
119
|
+
}
|
|
120
|
+
const newData = [...current.data, item];
|
|
121
|
+
this.write(section, newData, agentName);
|
|
122
|
+
}
|
|
123
|
+
has(section) {
|
|
124
|
+
return this.localCache.has(section);
|
|
125
|
+
}
|
|
126
|
+
delete(section) {
|
|
127
|
+
this.localCache.delete(section);
|
|
128
|
+
this.historyCache.delete(section);
|
|
129
|
+
this.subscriptions.delete(section);
|
|
130
|
+
void this.redis.del(this.sectionKey(section));
|
|
131
|
+
void this.redis.del(this.historyKey(section));
|
|
132
|
+
}
|
|
133
|
+
subscribe(section, handler) {
|
|
134
|
+
if (!this.subscriptions.has(section)) {
|
|
135
|
+
this.subscriptions.set(section, new Set());
|
|
136
|
+
}
|
|
137
|
+
this.subscriptions.get(section).add(handler);
|
|
138
|
+
return () => {
|
|
139
|
+
const handlers = this.subscriptions.get(section);
|
|
140
|
+
if (handlers) {
|
|
141
|
+
handlers.delete(handler);
|
|
142
|
+
if (handlers.size === 0) {
|
|
143
|
+
this.subscriptions.delete(section);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
getSections() {
|
|
149
|
+
return Array.from(this.localCache.keys());
|
|
150
|
+
}
|
|
151
|
+
getSection(section) {
|
|
152
|
+
const cached = this.localCache.get(section);
|
|
153
|
+
if (!cached)
|
|
154
|
+
return undefined;
|
|
155
|
+
return {
|
|
156
|
+
name: cached.name,
|
|
157
|
+
data: cached.data,
|
|
158
|
+
lastModified: cached.lastModified,
|
|
159
|
+
modifiedBy: cached.modifiedBy,
|
|
160
|
+
version: cached.version,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
getHistory(section) {
|
|
164
|
+
return this.historyCache.get(section) ?? [];
|
|
165
|
+
}
|
|
166
|
+
clear() {
|
|
167
|
+
const sections = Array.from(this.localCache.keys());
|
|
168
|
+
this.localCache.clear();
|
|
169
|
+
this.historyCache.clear();
|
|
170
|
+
for (const section of sections) {
|
|
171
|
+
void this.redis.del(this.sectionKey(section));
|
|
172
|
+
void this.redis.del(this.historyKey(section));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async close() {
|
|
176
|
+
await this.subscriber.unsubscribe();
|
|
177
|
+
await this.subscriber.quit();
|
|
178
|
+
}
|
|
179
|
+
async syncFromRedis() {
|
|
180
|
+
const pattern = `${this.keyPrefix}:${this.swarmId}:blackboard:*`;
|
|
181
|
+
const keys = await this.redis.keys(pattern);
|
|
182
|
+
for (const key of keys) {
|
|
183
|
+
if (key.endsWith(':history'))
|
|
184
|
+
continue;
|
|
185
|
+
const raw = await this.redis.get(key);
|
|
186
|
+
if (raw) {
|
|
187
|
+
const stored = JSON.parse(raw);
|
|
188
|
+
this.localCache.set(stored.name, stored);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
notifySubscribers(section, data, agentName) {
|
|
193
|
+
const handlers = this.subscriptions.get(section);
|
|
194
|
+
if (handlers) {
|
|
195
|
+
for (const handler of handlers) {
|
|
196
|
+
void Promise.resolve(handler(data, agentName)).catch((error) => {
|
|
197
|
+
console.warn('[RedisBlackboard] Handler error:', error);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=redis-blackboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-blackboard.js","sourceRoot":"","sources":["../../src/communication/redis-blackboard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SwarmEventEmitter, SwarmEventType, SwarmEvent, SwarmEventHandler } from '@cogitator-ai/types';
|
|
2
|
+
import type { Redis } from 'ioredis';
|
|
3
|
+
export interface RedisEventEmitterOptions {
|
|
4
|
+
redis: Redis;
|
|
5
|
+
swarmId: string;
|
|
6
|
+
keyPrefix?: string;
|
|
7
|
+
maxEvents?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class RedisSwarmEventEmitter implements SwarmEventEmitter {
|
|
10
|
+
private redis;
|
|
11
|
+
private subscriber;
|
|
12
|
+
private swarmId;
|
|
13
|
+
private keyPrefix;
|
|
14
|
+
private maxEvents;
|
|
15
|
+
private handlers;
|
|
16
|
+
private localEvents;
|
|
17
|
+
constructor(options: RedisEventEmitterOptions);
|
|
18
|
+
private eventsKey;
|
|
19
|
+
private channelKey;
|
|
20
|
+
initialize(): Promise<void>;
|
|
21
|
+
on(event: SwarmEventType | '*', handler: SwarmEventHandler): () => void;
|
|
22
|
+
once(event: SwarmEventType, handler: SwarmEventHandler): () => void;
|
|
23
|
+
emit(event: SwarmEventType, data?: unknown, agentName?: string): void;
|
|
24
|
+
emitAsync(event: SwarmEventType, data?: unknown, agentName?: string): Promise<void>;
|
|
25
|
+
off(event: SwarmEventType | '*', handler: SwarmEventHandler): void;
|
|
26
|
+
removeAllListeners(event?: SwarmEventType): void;
|
|
27
|
+
getEvents(): SwarmEvent[];
|
|
28
|
+
getEventsAsync(): Promise<SwarmEvent[]>;
|
|
29
|
+
getEventsByType(type: SwarmEventType): SwarmEvent[];
|
|
30
|
+
getEventsByAgent(agentName: string): SwarmEvent[];
|
|
31
|
+
clearEvents(): void;
|
|
32
|
+
clearEventsAsync(): Promise<void>;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
private notifyHandlers;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=redis-event-emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-event-emitter.d.ts","sourceRoot":"","sources":["../../src/communication/redis-event-emitter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,sBAAuB,YAAW,iBAAiB;IAC9D,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAA2D;IAC3E,OAAO,CAAC,WAAW,CAAoB;gBAE3B,OAAO,EAAE,wBAAwB;IAQ7C,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,UAAU;IAIZ,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAejC,EAAE,CAAC,KAAK,EAAE,cAAc,GAAG,GAAG,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,IAAI;IASvE,IAAI,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAUnE,IAAI,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAI/D,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBzF,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,GAAG,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAUlE,kBAAkB,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,IAAI;IAQhD,SAAS,IAAI,UAAU,EAAE;IAInB,cAAc,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAK7C,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,EAAE;IAInD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,EAAE;IAIjD,WAAW,IAAI,IAAI;IAIb,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAKjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,OAAO,CAAC,cAAc;CAmBvB"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export class RedisSwarmEventEmitter {
|
|
2
|
+
redis;
|
|
3
|
+
subscriber;
|
|
4
|
+
swarmId;
|
|
5
|
+
keyPrefix;
|
|
6
|
+
maxEvents;
|
|
7
|
+
handlers = new Map();
|
|
8
|
+
localEvents = [];
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.redis = options.redis;
|
|
11
|
+
this.subscriber = options.redis.duplicate();
|
|
12
|
+
this.swarmId = options.swarmId;
|
|
13
|
+
this.keyPrefix = options.keyPrefix ?? 'swarm';
|
|
14
|
+
this.maxEvents = options.maxEvents ?? 1000;
|
|
15
|
+
}
|
|
16
|
+
eventsKey() {
|
|
17
|
+
return `${this.keyPrefix}:${this.swarmId}:events`;
|
|
18
|
+
}
|
|
19
|
+
channelKey() {
|
|
20
|
+
return `${this.keyPrefix}:${this.swarmId}:events:live`;
|
|
21
|
+
}
|
|
22
|
+
async initialize() {
|
|
23
|
+
await this.subscriber.subscribe(this.channelKey());
|
|
24
|
+
this.subscriber.on('message', (_channel, messageJson) => {
|
|
25
|
+
try {
|
|
26
|
+
const event = JSON.parse(messageJson);
|
|
27
|
+
this.localEvents.push(event);
|
|
28
|
+
if (this.localEvents.length > this.maxEvents) {
|
|
29
|
+
this.localEvents = this.localEvents.slice(-this.maxEvents);
|
|
30
|
+
}
|
|
31
|
+
this.notifyHandlers(event);
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
on(event, handler) {
|
|
37
|
+
if (!this.handlers.has(event)) {
|
|
38
|
+
this.handlers.set(event, new Set());
|
|
39
|
+
}
|
|
40
|
+
this.handlers.get(event).add(handler);
|
|
41
|
+
return () => this.off(event, handler);
|
|
42
|
+
}
|
|
43
|
+
once(event, handler) {
|
|
44
|
+
const wrapper = (e) => {
|
|
45
|
+
this.off(event, wrapper);
|
|
46
|
+
void Promise.resolve(handler(e)).catch((error) => {
|
|
47
|
+
console.warn('[RedisSwarmEventEmitter] Once handler error:', error);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
return this.on(event, wrapper);
|
|
51
|
+
}
|
|
52
|
+
emit(event, data, agentName) {
|
|
53
|
+
void this.emitAsync(event, data, agentName);
|
|
54
|
+
}
|
|
55
|
+
async emitAsync(event, data, agentName) {
|
|
56
|
+
const swarmEvent = {
|
|
57
|
+
type: event,
|
|
58
|
+
timestamp: Date.now(),
|
|
59
|
+
agentName,
|
|
60
|
+
data,
|
|
61
|
+
};
|
|
62
|
+
const eventJson = JSON.stringify(swarmEvent);
|
|
63
|
+
await this.redis.rpush(this.eventsKey(), eventJson);
|
|
64
|
+
const len = await this.redis.llen(this.eventsKey());
|
|
65
|
+
if (len > this.maxEvents) {
|
|
66
|
+
await this.redis.ltrim(this.eventsKey(), len - this.maxEvents, -1);
|
|
67
|
+
}
|
|
68
|
+
await this.redis.publish(this.channelKey(), eventJson);
|
|
69
|
+
}
|
|
70
|
+
off(event, handler) {
|
|
71
|
+
const handlers = this.handlers.get(event);
|
|
72
|
+
if (handlers) {
|
|
73
|
+
handlers.delete(handler);
|
|
74
|
+
if (handlers.size === 0) {
|
|
75
|
+
this.handlers.delete(event);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
removeAllListeners(event) {
|
|
80
|
+
if (event) {
|
|
81
|
+
this.handlers.delete(event);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
this.handlers.clear();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
getEvents() {
|
|
88
|
+
return [...this.localEvents];
|
|
89
|
+
}
|
|
90
|
+
async getEventsAsync() {
|
|
91
|
+
const raw = await this.redis.lrange(this.eventsKey(), 0, -1);
|
|
92
|
+
return raw.map((r) => JSON.parse(r));
|
|
93
|
+
}
|
|
94
|
+
getEventsByType(type) {
|
|
95
|
+
return this.localEvents.filter((e) => e.type === type);
|
|
96
|
+
}
|
|
97
|
+
getEventsByAgent(agentName) {
|
|
98
|
+
return this.localEvents.filter((e) => e.agentName === agentName);
|
|
99
|
+
}
|
|
100
|
+
clearEvents() {
|
|
101
|
+
this.localEvents = [];
|
|
102
|
+
}
|
|
103
|
+
async clearEventsAsync() {
|
|
104
|
+
await this.redis.del(this.eventsKey());
|
|
105
|
+
this.localEvents = [];
|
|
106
|
+
}
|
|
107
|
+
async close() {
|
|
108
|
+
await this.subscriber.unsubscribe();
|
|
109
|
+
await this.subscriber.quit();
|
|
110
|
+
}
|
|
111
|
+
notifyHandlers(event) {
|
|
112
|
+
const handlers = this.handlers.get(event.type);
|
|
113
|
+
if (handlers) {
|
|
114
|
+
for (const handler of handlers) {
|
|
115
|
+
void Promise.resolve(handler(event)).catch((error) => {
|
|
116
|
+
console.warn('[RedisSwarmEventEmitter] Handler error:', error);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const wildcardHandlers = this.handlers.get('*');
|
|
121
|
+
if (wildcardHandlers) {
|
|
122
|
+
for (const handler of wildcardHandlers) {
|
|
123
|
+
void Promise.resolve(handler(event)).catch((error) => {
|
|
124
|
+
console.warn('[RedisSwarmEventEmitter] Wildcard handler error:', error);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=redis-event-emitter.js.map
|