multi-agent-sharing.ts•22.6 kB
/**
* Multi-Agent Memory Sharing System for DocuMCP
* Implements Issue #50: Multi-Agent Memory Sharing
*
* Enables multiple DocuMCP instances to share and synchronize memory,
* creating a collaborative knowledge network for enhanced learning and recommendations.
*/
import { MemoryManager } from './manager.js';
import { MemoryEntry } from './storage.js';
import { EventEmitter } from 'events';
import * as crypto from 'crypto';
export interface AgentIdentity {
id: string;
name: string;
version: string;
capabilities: string[];
lastSeen: string;
trustLevel: 'untrusted' | 'low' | 'medium' | 'high' | 'trusted';
specializations: string[];
}
export interface SharedMemory {
originalEntry: MemoryEntry;
sharingMetadata: {
sourceAgent: string;
sharedAt: string;
accessCount: number;
trustScore: number;
validatedBy: string[];
conflicts: string[];
};
transformations: Array<{
agentId: string;
transformationType: 'anonymization' | 'aggregation' | 'enrichment' | 'validation';
appliedAt: string;
details: Record<string, any>;
}>;
}
export interface SyncRequest {
id: string;
fromAgent: string;
toAgent: string;
requestType: 'full_sync' | 'incremental' | 'selective' | 'validation';
criteria?: {
types?: string[];
tags?: string[];
timeRange?: { start: string; end: string };
minTrustLevel?: number;
};
requestedAt: string;
status: 'pending' | 'in_progress' | 'completed' | 'failed';
}
export interface ConflictResolution {
conflictId: string;
conflictType: 'duplicate' | 'contradiction' | 'version_mismatch' | 'trust_dispute';
involvedEntries: string[];
involvedAgents: string[];
resolutionStrategy: 'merge' | 'prioritize_trusted' | 'manual_review' | 'temporal_precedence';
resolvedAt?: string;
resolution?: {
action: string;
resultingEntry?: MemoryEntry;
metadata: Record<string, any>;
};
}
export interface CollaborativeInsight {
id: string;
type: 'trend' | 'pattern' | 'anomaly' | 'consensus' | 'disagreement';
description: string;
evidence: string[];
contributingAgents: string[];
confidence: number;
generatedAt: string;
actionable: boolean;
recommendations?: string[];
}
export class MultiAgentMemorySharing extends EventEmitter {
private memoryManager: MemoryManager;
private agentId: string;
private knownAgents: Map<string, AgentIdentity>;
private sharedMemories: Map<string, SharedMemory>;
private syncRequests: Map<string, SyncRequest>;
private conflicts: Map<string, ConflictResolution>;
private collaborativeInsights: Map<string, CollaborativeInsight>;
private syncInterval: NodeJS.Timeout | null = null;
constructor(memoryManager: MemoryManager, agentIdentity: Partial<AgentIdentity>) {
super();
this.memoryManager = memoryManager;
this.agentId = agentIdentity.id || this.generateAgentId();
this.knownAgents = new Map();
this.sharedMemories = new Map();
this.syncRequests = new Map();
this.conflicts = new Map();
this.collaborativeInsights = new Map();
// Register self
this.registerAgent({
id: this.agentId,
name: agentIdentity.name || 'DocuMCP Agent',
version: agentIdentity.version || '1.0.0',
capabilities: agentIdentity.capabilities || ['analysis', 'recommendation', 'deployment'],
lastSeen: new Date().toISOString(),
trustLevel: 'trusted',
specializations: agentIdentity.specializations || [],
});
}
/**
* Initialize multi-agent sharing
*/
async initialize(): Promise<void> {
await this.loadSharedMemories();
await this.loadKnownAgents();
await this.loadPendingSyncRequests();
// Start periodic sync
this.startPeriodicSync();
this.emit('initialized', { agentId: this.agentId });
}
/**
* Register a new agent in the network
*/
async registerAgent(agent: AgentIdentity): Promise<void> {
this.knownAgents.set(agent.id, {
...agent,
lastSeen: new Date().toISOString(),
});
await this.persistAgentRegistry();
this.emit('agent-registered', agent);
}
/**
* Share a memory entry with other agents
*/
async shareMemory(
memoryId: string,
targetAgents?: string[],
options?: {
anonymize?: boolean;
requireValidation?: boolean;
trustLevel?: number;
},
): Promise<SharedMemory> {
const memory = await this.memoryManager.recall(memoryId);
if (!memory) {
throw new Error(`Memory ${memoryId} not found`);
}
// Create shared memory wrapper
const sharedMemory: SharedMemory = {
originalEntry: this.anonymizeIfRequired(memory, options?.anonymize),
sharingMetadata: {
sourceAgent: this.agentId,
sharedAt: new Date().toISOString(),
accessCount: 0,
trustScore: this.calculateInitialTrustScore(memory),
validatedBy: [],
conflicts: [],
},
transformations: [],
};
// Apply anonymization transformation if required
if (options?.anonymize) {
sharedMemory.transformations.push({
agentId: this.agentId,
transformationType: 'anonymization',
appliedAt: new Date().toISOString(),
details: { level: 'standard', preserveStructure: true },
});
}
this.sharedMemories.set(memoryId, sharedMemory);
// Create sync requests for target agents
if (targetAgents) {
for (const targetAgent of targetAgents) {
await this.createSyncRequest(targetAgent, 'selective', {
memoryIds: [memoryId],
});
}
} else {
// Broadcast to all trusted agents
await this.broadcastToTrustedAgents(sharedMemory);
}
await this.persistSharedMemories();
this.emit('memory-shared', { memoryId, sharedMemory });
return sharedMemory;
}
/**
* Receive shared memory from another agent
*/
async receiveSharedMemory(
sharedMemory: SharedMemory,
sourceAgent: string,
): Promise<{
accepted: boolean;
conflicts?: ConflictResolution[];
integrationResult?: string;
}> {
// Validate source agent trust level
const sourceAgentInfo = this.knownAgents.get(sourceAgent);
if (!sourceAgentInfo || sourceAgentInfo.trustLevel === 'untrusted') {
return { accepted: false };
}
// Check for conflicts with existing memories
const conflicts = await this.detectConflicts(sharedMemory);
if (conflicts.length > 0) {
// Store conflicts for resolution
for (const conflict of conflicts) {
this.conflicts.set(conflict.conflictId, conflict);
await this.resolveConflict(conflict);
}
this.emit('conflict-detected', { conflicts, sharedMemory });
}
// Integrate the shared memory
const integrationResult = await this.integrateSharedMemory(sharedMemory, sourceAgent);
// Update sharing metadata
sharedMemory.sharingMetadata.accessCount++;
this.emit('memory-received', { sharedMemory, sourceAgent, integrationResult });
return {
accepted: true,
conflicts: conflicts.length > 0 ? conflicts : undefined,
integrationResult,
};
}
/**
* Request synchronization with another agent
*/
async requestSync(
targetAgent: string,
syncType: SyncRequest['requestType'] = 'incremental',
criteria?: SyncRequest['criteria'],
): Promise<SyncRequest> {
const syncRequest: SyncRequest = {
id: this.generateSyncId(),
fromAgent: this.agentId,
toAgent: targetAgent,
requestType: syncType,
criteria,
requestedAt: new Date().toISOString(),
status: 'pending',
};
this.syncRequests.set(syncRequest.id, syncRequest);
await this.persistSyncRequests();
this.emit('sync-requested', syncRequest);
return syncRequest;
}
/**
* Process incoming sync request
*/
async processSyncRequest(syncRequest: SyncRequest): Promise<{
approved: boolean;
memories?: SharedMemory[];
reason?: string;
}> {
// Validate requesting agent
const requestingAgent = this.knownAgents.get(syncRequest.fromAgent);
if (!requestingAgent || requestingAgent.trustLevel === 'untrusted') {
return { approved: false, reason: 'Untrusted agent' };
}
// Update request status
syncRequest.status = 'in_progress';
this.syncRequests.set(syncRequest.id, syncRequest);
try {
// Get memories based on sync type and criteria
const memories = await this.getMemoriesForSync(syncRequest);
syncRequest.status = 'completed';
this.emit('sync-completed', { syncRequest, memoriesCount: memories.length });
return { approved: true, memories };
} catch (error) {
syncRequest.status = 'failed';
this.emit('sync-failed', { syncRequest, error });
return { approved: false, reason: error instanceof Error ? error.message : 'Unknown error' };
}
}
/**
* Generate collaborative insights from shared memories
*/
async generateCollaborativeInsights(): Promise<CollaborativeInsight[]> {
const insights: CollaborativeInsight[] = [];
// Analyze trends across agents
const trends = await this.analyzeTrends();
insights.push(...trends);
// Find consensus patterns
const consensus = await this.findConsensusPatterns();
insights.push(...consensus);
// Identify disagreements that need attention
const disagreements = await this.identifyDisagreements();
insights.push(...disagreements);
// Detect anomalies
const anomalies = await this.detectAnomalies();
insights.push(...anomalies);
// Store insights
for (const insight of insights) {
this.collaborativeInsights.set(insight.id, insight);
}
await this.persistCollaborativeInsights();
this.emit('insights-generated', { count: insights.length });
return insights;
}
/**
* Validate shared memory against local knowledge
*/
async validateSharedMemory(sharedMemory: SharedMemory): Promise<{
isValid: boolean;
confidence: number;
issues: string[];
recommendations: string[];
}> {
const issues: string[] = [];
const recommendations: string[] = [];
let confidence = 1.0;
// Check data consistency
if (!this.validateDataStructure(sharedMemory.originalEntry)) {
issues.push('Invalid data structure');
confidence *= 0.7;
}
// Cross-validate with local memories
const similarMemories = await this.findSimilarLocalMemories(sharedMemory.originalEntry);
if (similarMemories.length > 0) {
const consistencyScore = this.calculateConsistency(
sharedMemory.originalEntry,
similarMemories,
);
if (consistencyScore < 0.8) {
issues.push('Inconsistent with local knowledge');
confidence *= consistencyScore;
}
}
// Check source agent reliability
const sourceAgent = this.knownAgents.get(sharedMemory.sharingMetadata.sourceAgent);
if (sourceAgent) {
if (sourceAgent.trustLevel === 'low') {
confidence *= 0.8;
recommendations.push('Verify with additional sources');
} else if (sourceAgent.trustLevel === 'high' || sourceAgent.trustLevel === 'trusted') {
confidence *= 1.1;
}
}
// Validate transformations
for (const transformation of sharedMemory.transformations) {
if (transformation.transformationType === 'anonymization') {
// Check if anonymization preserved essential information
if (!this.validateAnonymization(transformation)) {
issues.push('Anonymization may have removed critical information');
confidence *= 0.9;
}
}
}
return {
isValid: issues.length === 0 || confidence > 0.6,
confidence: Math.min(confidence, 1.0),
issues,
recommendations,
};
}
/**
* Get network statistics
*/
getNetworkStatistics(): {
connectedAgents: number;
sharedMemories: number;
activeSyncs: number;
resolvedConflicts: number;
trustDistribution: Record<string, number>;
collaborativeInsights: number;
networkHealth: number;
} {
const trustDistribution: Record<string, number> = {};
for (const agent of this.knownAgents.values()) {
trustDistribution[agent.trustLevel] = (trustDistribution[agent.trustLevel] || 0) + 1;
}
const activeSyncs = Array.from(this.syncRequests.values()).filter(
(req) => req.status === 'pending' || req.status === 'in_progress',
).length;
const resolvedConflicts = Array.from(this.conflicts.values()).filter(
(conflict) => conflict.resolvedAt,
).length;
// Calculate network health (0-1)
const trustedAgents = Array.from(this.knownAgents.values()).filter(
(agent) => agent.trustLevel === 'high' || agent.trustLevel === 'trusted',
).length;
const totalAgents = this.knownAgents.size;
const networkHealth = totalAgents > 0 ? trustedAgents / totalAgents : 0;
return {
connectedAgents: this.knownAgents.size,
sharedMemories: this.sharedMemories.size,
activeSyncs,
resolvedConflicts,
trustDistribution,
collaborativeInsights: this.collaborativeInsights.size,
networkHealth,
};
}
// Private helper methods
private generateAgentId(): string {
return `agent_${crypto.randomBytes(8).toString('hex')}`;
}
private generateSyncId(): string {
return `sync_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
}
private anonymizeIfRequired(memory: MemoryEntry, anonymize?: boolean): MemoryEntry {
if (!anonymize) return memory;
// Create anonymized copy
const anonymized = JSON.parse(JSON.stringify(memory));
// Remove/hash sensitive information
if (anonymized.metadata.repository) {
anonymized.metadata.repository = this.hashSensitiveData(anonymized.metadata.repository);
}
if (anonymized.metadata.projectId) {
anonymized.metadata.projectId = this.hashSensitiveData(anonymized.metadata.projectId);
}
// Remove file paths and specific identifiers
if (anonymized.data.files) {
delete anonymized.data.files;
}
return anonymized;
}
private hashSensitiveData(data: string): string {
return crypto.createHash('sha256').update(data).digest('hex').substring(0, 16);
}
private calculateInitialTrustScore(memory: MemoryEntry): number {
let score = 0.5; // Base score
// Boost for successful outcomes
if (memory.data.status === 'success') score += 0.2;
// Boost for rich metadata
if (memory.metadata.tags && memory.metadata.tags.length > 2) score += 0.1;
// Boost for recent data
const daysSince = (Date.now() - new Date(memory.timestamp).getTime()) / (1000 * 60 * 60 * 24);
if (daysSince <= 30) score += 0.1;
// Boost for complete data
if (memory.checksum) score += 0.1;
return Math.min(score, 1.0);
}
private async detectConflicts(sharedMemory: SharedMemory): Promise<ConflictResolution[]> {
const conflicts: ConflictResolution[] = [];
// Check for duplicates
const similarLocal = await this.findSimilarLocalMemories(sharedMemory.originalEntry);
for (const similar of similarLocal) {
if (this.isLikelyDuplicate(sharedMemory.originalEntry, similar)) {
conflicts.push({
conflictId: `conflict_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`,
conflictType: 'duplicate',
involvedEntries: [sharedMemory.originalEntry.id, similar.id],
involvedAgents: [sharedMemory.sharingMetadata.sourceAgent, this.agentId],
resolutionStrategy: 'merge',
});
}
}
return conflicts;
}
private async resolveConflict(conflict: ConflictResolution): Promise<void> {
// Implement conflict resolution based on strategy
switch (conflict.resolutionStrategy) {
case 'merge':
await this.mergeConflictingEntries(conflict);
break;
case 'prioritize_trusted':
await this.prioritizeTrustedSource(conflict);
break;
case 'temporal_precedence':
await this.useTemporalPrecedence(conflict);
break;
default:
// Mark for manual review
conflict.resolutionStrategy = 'manual_review';
}
conflict.resolvedAt = new Date().toISOString();
this.conflicts.set(conflict.conflictId, conflict);
}
private async integrateSharedMemory(
sharedMemory: SharedMemory,
sourceAgent: string,
): Promise<string> {
// Add transformation for integration
sharedMemory.transformations.push({
agentId: this.agentId,
transformationType: 'enrichment',
appliedAt: new Date().toISOString(),
details: { integratedFrom: sourceAgent },
});
// Store in local memory with special metadata
const enrichedEntry = {
...sharedMemory.originalEntry,
metadata: {
...sharedMemory.originalEntry.metadata,
sharedFrom: sourceAgent,
integratedAt: new Date().toISOString(),
tags: [...(sharedMemory.originalEntry.metadata.tags || []), 'shared', 'collaborative'],
},
};
await this.memoryManager.remember(
enrichedEntry.type,
enrichedEntry.data,
enrichedEntry.metadata,
);
return 'integrated_successfully';
}
private async getMemoriesForSync(syncRequest: SyncRequest): Promise<SharedMemory[]> {
const allMemories = await this.memoryManager.search('', { sortBy: 'timestamp' });
let filteredMemories = allMemories;
// Apply criteria filtering
if (syncRequest.criteria) {
if (syncRequest.criteria.types) {
filteredMemories = filteredMemories.filter((m) =>
syncRequest.criteria!.types!.includes(m.type),
);
}
if (syncRequest.criteria.tags) {
filteredMemories = filteredMemories.filter(
(m) => m.metadata.tags?.some((tag) => syncRequest.criteria!.tags!.includes(tag)),
);
}
if (syncRequest.criteria.timeRange) {
const start = new Date(syncRequest.criteria.timeRange.start);
const end = new Date(syncRequest.criteria.timeRange.end);
filteredMemories = filteredMemories.filter((m) => {
const memTime = new Date(m.timestamp);
return memTime >= start && memTime <= end;
});
}
}
// Convert to shared memories
return filteredMemories.map((memory) => ({
originalEntry: memory,
sharingMetadata: {
sourceAgent: this.agentId,
sharedAt: new Date().toISOString(),
accessCount: 0,
trustScore: this.calculateInitialTrustScore(memory),
validatedBy: [],
conflicts: [],
},
transformations: [],
}));
}
private async analyzeTrends(): Promise<CollaborativeInsight[]> {
// Analyze shared memories to identify trends
return []; // Placeholder implementation
}
private async findConsensusPatterns(): Promise<CollaborativeInsight[]> {
// Find patterns where multiple agents agree
return []; // Placeholder implementation
}
private async identifyDisagreements(): Promise<CollaborativeInsight[]> {
// Find areas where agents disagree
return []; // Placeholder implementation
}
private async detectAnomalies(): Promise<CollaborativeInsight[]> {
// Detect unusual patterns in shared data
return []; // Placeholder implementation
}
private validateDataStructure(entry: MemoryEntry): boolean {
return Boolean(entry.id && entry.timestamp && entry.type && entry.data);
}
private async findSimilarLocalMemories(entry: MemoryEntry): Promise<MemoryEntry[]> {
// Find similar memories in local storage
return this.memoryManager.search(entry.metadata.projectId || '', { sortBy: 'timestamp' });
}
private calculateConsistency(_entry: MemoryEntry, _similar: MemoryEntry[]): number {
// Calculate consistency score (placeholder)
return 0.8;
}
private validateAnonymization(_transformation: any): boolean {
// Validate anonymization transformation (placeholder)
return true;
}
private isLikelyDuplicate(entry1: MemoryEntry, entry2: MemoryEntry): boolean {
// Simple duplicate detection
return (
entry1.type === entry2.type &&
entry1.metadata.projectId === entry2.metadata.projectId &&
Math.abs(new Date(entry1.timestamp).getTime() - new Date(entry2.timestamp).getTime()) < 60000
); // 1 minute
}
private async mergeConflictingEntries(_conflict: ConflictResolution): Promise<void> {
// Merge conflicting entries (placeholder)
}
private async prioritizeTrustedSource(_conflict: ConflictResolution): Promise<void> {
// Prioritize trusted source (placeholder)
}
private async useTemporalPrecedence(_conflict: ConflictResolution): Promise<void> {
// Use temporal precedence (placeholder)
}
private async broadcastToTrustedAgents(_sharedMemory: SharedMemory): Promise<void> {
// Broadcast to trusted agents (placeholder)
}
private startPeriodicSync(): void {
this.syncInterval = setInterval(
async () => {
await this.performPeriodicSync();
},
5 * 60 * 1000,
); // Every 5 minutes
}
private async performPeriodicSync(): Promise<void> {
// Perform periodic synchronization with trusted agents
}
private async loadSharedMemories(): Promise<void> {
// Load shared memories from persistence
}
private async loadKnownAgents(): Promise<void> {
// Load known agents from persistence
}
private async loadPendingSyncRequests(): Promise<void> {
// Load pending sync requests from persistence
}
private async persistSharedMemories(): Promise<void> {
// Persist shared memories
}
private async persistAgentRegistry(): Promise<void> {
// Persist agent registry
}
private async persistSyncRequests(): Promise<void> {
// Persist sync requests
}
private async persistCollaborativeInsights(): Promise<void> {
// Persist collaborative insights
}
private async createSyncRequest(
_targetAgent: string,
_type: SyncRequest['requestType'],
_options: any,
): Promise<void> {
// Create sync request (placeholder)
}
/**
* Cleanup and shutdown
*/
async shutdown(): Promise<void> {
if (this.syncInterval) {
clearInterval(this.syncInterval);
}
await this.persistSharedMemories();
await this.persistAgentRegistry();
await this.persistSyncRequests();
await this.persistCollaborativeInsights();
this.emit('shutdown', { agentId: this.agentId });
}
}
export default MultiAgentMemorySharing;