@clduab11/gemini-flow
Version:
Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.
981 lines (836 loc) • 25.3 kB
text/typescript
/**
* CRDT-Based State Synchronization for A2A Memory Coordination
*
* Implements Conflict-free Replicated Data Types for eventual consistency:
* - G-Counter (Grow-only Counter)
* - PN-Counter (Positive-Negative Counter)
* - G-Set (Grow-only Set)
* - OR-Set (Observed-Remove Set)
* - LWW-Register (Last-Writer-Wins Register)
* - Multi-Value Register with vector clocks
* - CRDT-based Maps and Arrays
*/
import { EventEmitter } from "events";
import { Logger } from "../../../utils/logger.js";
import { VectorClock } from "./vector-clocks.js";
export type CRDTType =
| "g-counter"
| "pn-counter"
| "g-set"
| "or-set"
| "lww-register"
| "mv-register"
| "crdt-map"
| "crdt-array";
export interface CRDT {
type: CRDTType;
id: string;
value: any;
vectorClock: VectorClock;
metadata: {
agentId: string;
timestamp: Date;
version: number;
};
}
export interface CRDTOperation {
type: "increment" | "decrement" | "add" | "remove" | "set" | "merge";
crdtId: string;
crdtType: CRDTType;
key?: string;
value?: any;
vectorClock: VectorClock;
agentId: string;
timestamp: Date;
}
export interface SyncState {
lastSyncVector: VectorClock;
pendingOperations: CRDTOperation[];
conflictCount: number;
mergeCount: number;
lastSyncTime: Date;
}
/**
* G-Counter: Grow-only Counter CRDT
*/
class GCounter implements CRDT {
type: CRDTType = "g-counter";
id: string;
value: Map<string, number> = new Map();
vectorClock: VectorClock;
metadata: any;
constructor(id: string, agentId: string) {
this.id = id;
this.vectorClock = new VectorClock(agentId);
this.metadata = {
agentId,
timestamp: new Date(),
version: 1,
};
}
increment(agentId: string, amount: number = 1): void {
const current = this.value.get(agentId) || 0;
this.value.set(agentId, current + amount);
this.vectorClock.increment();
this.metadata.timestamp = new Date();
this.metadata.version++;
}
getValue(): number {
return Array.from(this.value.values()).reduce((sum, val) => sum + val, 0);
}
merge(other: GCounter): GCounter {
const merged = new GCounter(this.id, this.metadata.agentId);
merged.vectorClock = this.vectorClock.merge(other.vectorClock);
// Merge by taking maximum for each agent
const allAgents = new Set([...this.value.keys(), ...other.value.keys()]);
for (const agentId of allAgents) {
const thisValue = this.value.get(agentId) || 0;
const otherValue = other.value.get(agentId) || 0;
merged.value.set(agentId, Math.max(thisValue, otherValue));
}
merged.metadata.timestamp = new Date();
merged.metadata.version =
Math.max(this.metadata.version, other.metadata.version) + 1;
return merged;
}
}
/**
* PN-Counter: Positive-Negative Counter CRDT
*/
class PNCounter implements CRDT {
type: CRDTType = "pn-counter";
id: string;
value: { positive: GCounter; negative: GCounter };
vectorClock: VectorClock;
metadata: any;
constructor(id: string, agentId: string) {
this.id = id;
this.value = {
positive: new GCounter(`${id}_pos`, agentId),
negative: new GCounter(`${id}_neg`, agentId),
};
this.vectorClock = new VectorClock(agentId);
this.metadata = {
agentId,
timestamp: new Date(),
version: 1,
};
}
increment(agentId: string, amount: number = 1): void {
this.value.positive.increment(agentId, amount);
this.vectorClock.increment();
this.updateMetadata();
}
decrement(agentId: string, amount: number = 1): void {
this.value.negative.increment(agentId, amount);
this.vectorClock.increment();
this.updateMetadata();
}
getValue(): number {
return this.value.positive.getValue() - this.value.negative.getValue();
}
merge(other: PNCounter): PNCounter {
const merged = new PNCounter(this.id, this.metadata.agentId);
merged.value.positive = this.value.positive.merge(other.value.positive);
merged.value.negative = this.value.negative.merge(other.value.negative);
merged.vectorClock = this.vectorClock.merge(other.vectorClock);
merged.metadata.timestamp = new Date();
merged.metadata.version =
Math.max(this.metadata.version, other.metadata.version) + 1;
return merged;
}
private updateMetadata(): void {
this.metadata.timestamp = new Date();
this.metadata.version++;
}
}
/**
* OR-Set: Observed-Remove Set CRDT
*/
class ORSet implements CRDT {
type: CRDTType = "or-set";
id: string;
value: {
elements: Map<string, Set<string>>; // element -> set of unique tags
removed: Set<string>; // set of removed tags
};
vectorClock: VectorClock;
metadata: any;
constructor(id: string, agentId: string) {
this.id = id;
this.value = {
elements: new Map(),
removed: new Set(),
};
this.vectorClock = new VectorClock(agentId);
this.metadata = {
agentId,
timestamp: new Date(),
version: 1,
};
}
add(element: string, agentId: string): string {
const tag = `${agentId}_${Date.now()}_${Math.random()}`;
if (!this.value.elements.has(element)) {
this.value.elements.set(element, new Set());
}
this.value.elements.get(element)!.add(tag);
this.vectorClock.increment();
this.updateMetadata();
return tag;
}
remove(element: string): void {
const tags = this.value.elements.get(element);
if (tags) {
// Mark all tags as removed
for (const tag of tags) {
this.value.removed.add(tag);
}
}
this.vectorClock.increment();
this.updateMetadata();
}
contains(element: string): boolean {
const tags = this.value.elements.get(element);
if (!tags || tags.size === 0) return false;
// Element exists if any tag is not removed
for (const tag of tags) {
if (!this.value.removed.has(tag)) {
return true;
}
}
return false;
}
getElements(): Set<string> {
const result = new Set<string>();
for (const [element, tags] of this.value.elements) {
if (this.contains(element)) {
result.add(element);
}
}
return result;
}
merge(other: ORSet): ORSet {
const merged = new ORSet(this.id, this.metadata.agentId);
merged.vectorClock = this.vectorClock.merge(other.vectorClock);
// Merge elements (union of all tags)
const allElements = new Set([
...this.value.elements.keys(),
...other.value.elements.keys(),
]);
for (const element of allElements) {
const thisTags = this.value.elements.get(element) || new Set();
const otherTags = other.value.elements.get(element) || new Set();
const mergedTags = new Set([...thisTags, ...otherTags]);
merged.value.elements.set(element, mergedTags);
}
// Merge removed tags (union)
merged.value.removed = new Set([
...this.value.removed,
...other.value.removed,
]);
merged.updateMetadata();
return merged;
}
private updateMetadata(): void {
this.metadata.timestamp = new Date();
this.metadata.version++;
}
}
/**
* LWW-Register: Last-Writer-Wins Register CRDT
*/
class LWWRegister implements CRDT {
type: CRDTType = "lww-register";
id: string;
value: {
data: any;
timestamp: number;
agentId: string;
};
vectorClock: VectorClock;
metadata: any;
constructor(id: string, agentId: string, initialValue?: any) {
this.id = id;
this.value = {
data: initialValue,
timestamp: Date.now(),
agentId,
};
this.vectorClock = new VectorClock(agentId);
this.metadata = {
agentId,
timestamp: new Date(),
version: 1,
};
}
set(value: any, agentId: string): void {
this.value = {
data: value,
timestamp: Date.now(),
agentId,
};
this.vectorClock.increment();
this.updateMetadata();
}
get(): any {
return this.value.data;
}
merge(other: LWWRegister): LWWRegister {
const merged = new LWWRegister(this.id, this.metadata.agentId);
merged.vectorClock = this.vectorClock.merge(other.vectorClock);
// Choose value with latest timestamp (LWW semantics)
if (this.value.timestamp > other.value.timestamp) {
merged.value = { ...this.value };
} else if (this.value.timestamp < other.value.timestamp) {
merged.value = { ...other.value };
} else {
// Tie-breaking by agent ID (deterministic)
merged.value =
this.value.agentId > other.value.agentId
? { ...this.value }
: { ...other.value };
}
merged.updateMetadata();
return merged;
}
private updateMetadata(): void {
this.metadata.timestamp = new Date();
this.metadata.version++;
}
}
/**
* Multi-Value Register with Vector Clocks
*/
class MVRegister implements CRDT {
type: CRDTType = "mv-register";
id: string;
value: Map<string, { data: any; vectorClock: VectorClock }>;
vectorClock: VectorClock;
metadata: any;
constructor(id: string, agentId: string) {
this.id = id;
this.value = new Map();
this.vectorClock = new VectorClock(agentId);
this.metadata = {
agentId,
timestamp: new Date(),
version: 1,
};
}
set(value: any, agentId: string): void {
const agentClock = new VectorClock(agentId);
agentClock.increment();
this.value.set(agentId, {
data: value,
vectorClock: agentClock,
});
this.vectorClock.increment();
this.updateMetadata();
}
get(): any[] {
return Array.from(this.value.values()).map((v) => v.data);
}
getConcurrentValues(): any[] {
const concurrent: any[] = [];
const values = Array.from(this.value.values());
for (const value of values) {
let isConcurrent = true;
for (const other of values) {
if (value !== other) {
const comparison = value.vectorClock.compare(other.vectorClock);
if (comparison === "before") {
isConcurrent = false;
break;
}
}
}
if (isConcurrent) {
concurrent.push(value.data);
}
}
return concurrent;
}
merge(other: MVRegister): MVRegister {
const merged = new MVRegister(this.id, this.metadata.agentId);
merged.vectorClock = this.vectorClock.merge(other.vectorClock);
// Merge all values, keeping only concurrent ones
const allAgents = new Set([...this.value.keys(), ...other.value.keys()]);
for (const agentId of allAgents) {
const thisValue = this.value.get(agentId);
const otherValue = other.value.get(agentId);
if (thisValue && otherValue) {
// Take the one with higher vector clock
const comparison = thisValue.vectorClock.compare(
otherValue.vectorClock,
);
if (comparison === "after" || comparison === "concurrent") {
merged.value.set(agentId, thisValue);
} else {
merged.value.set(agentId, otherValue);
}
} else if (thisValue) {
merged.value.set(agentId, thisValue);
} else if (otherValue) {
merged.value.set(agentId, otherValue);
}
}
merged.updateMetadata();
return merged;
}
private updateMetadata(): void {
this.metadata.timestamp = new Date();
this.metadata.version++;
}
}
/**
* CRDT-based Map
*/
class CRDTMap implements CRDT {
type: CRDTType = "crdt-map";
id: string;
value: Map<string, CRDT>;
vectorClock: VectorClock;
metadata: any;
constructor(id: string, agentId: string) {
this.id = id;
this.value = new Map();
this.vectorClock = new VectorClock(agentId);
this.metadata = {
agentId,
timestamp: new Date(),
version: 1,
};
}
set(key: string, crdt: CRDT): void {
this.value.set(key, crdt);
this.vectorClock.increment();
this.updateMetadata();
}
get(key: string): CRDT | undefined {
return this.value.get(key);
}
delete(key: string): void {
this.value.delete(key);
this.vectorClock.increment();
this.updateMetadata();
}
keys(): IterableIterator<string> {
return this.value.keys();
}
merge(other: CRDTMap): CRDTMap {
const merged = new CRDTMap(this.id, this.metadata.agentId);
merged.vectorClock = this.vectorClock.merge(other.vectorClock);
const allKeys = new Set([...this.value.keys(), ...other.value.keys()]);
for (const key of allKeys) {
const thisCrdt = this.value.get(key);
const otherCrdt = other.value.get(key);
if (thisCrdt && otherCrdt && thisCrdt.type === otherCrdt.type) {
// Merge CRDTs of the same type
merged.value.set(key, this.mergeCRDTs(thisCrdt, otherCrdt));
} else if (thisCrdt) {
merged.value.set(key, thisCrdt);
} else if (otherCrdt) {
merged.value.set(key, otherCrdt);
}
}
merged.updateMetadata();
return merged;
}
private mergeCRDTs(a: CRDT, b: CRDT): CRDT {
// Type-specific merging
switch (a.type) {
case "g-counter":
return (a as GCounter).merge(b as GCounter);
case "pn-counter":
return (a as PNCounter).merge(b as PNCounter);
case "or-set":
return (a as ORSet).merge(b as ORSet);
case "lww-register":
return (a as LWWRegister).merge(b as LWWRegister);
case "mv-register":
return (a as MVRegister).merge(b as MVRegister);
default:
return a; // Fallback
}
}
private updateMetadata(): void {
this.metadata.timestamp = new Date();
this.metadata.version++;
}
}
/**
* Main CRDT Synchronizer
* Note: CRDTs use eventual consistency and do not require quorum-based consensus.
* All operations are commutative and convergent, allowing for asynchronous merging.
*/
export class CRDTSynchronizer extends EventEmitter {
private logger: Logger;
private agentId: string;
private vectorClock: VectorClock;
private crdts: Map<string, CRDT> = new Map();
private syncStates: Map<string, SyncState> = new Map();
private operationLog: CRDTOperation[] = [];
constructor(agentId: string, vectorClock: VectorClock) {
super();
this.logger = new Logger(`CRDTSynchronizer:${agentId}`);
this.agentId = agentId;
this.vectorClock = vectorClock;
this.logger.info("CRDT Synchronizer initialized", { agentId });
}
/**
* Create a new CRDT
*/
createCRDT<T extends CRDT>(
id: string,
type: CRDTType,
initialValue?: any,
): T {
let crdt: CRDT;
switch (type) {
case "g-counter":
crdt = new GCounter(id, this.agentId);
break;
case "pn-counter":
crdt = new PNCounter(id, this.agentId);
break;
case "or-set":
crdt = new ORSet(id, this.agentId);
break;
case "lww-register":
crdt = new LWWRegister(id, this.agentId, initialValue);
break;
case "mv-register":
crdt = new MVRegister(id, this.agentId);
break;
case "crdt-map":
crdt = new CRDTMap(id, this.agentId);
break;
default:
throw new Error(`Unsupported CRDT type: ${type}`);
}
this.crdts.set(id, crdt);
this.logger.debug("CRDT created", { id, type });
this.emit("crdt_created", { id, type, crdt });
return crdt as T;
}
/**
* Get existing CRDT
*/
getCRDT<T extends CRDT>(id: string): T | undefined {
return this.crdts.get(id) as T;
}
/**
* Apply operation to CRDT
*/
async applyOperation(operation: CRDTOperation): Promise<boolean> {
try {
const crdt = this.crdts.get(operation.crdtId);
if (!crdt) {
this.logger.warn("CRDT not found for operation", {
crdtId: operation.crdtId,
operation: operation.type,
});
return false;
}
// Check if we should apply this operation
if (!this.shouldApplyOperation(operation, crdt)) {
return false;
}
// Apply operation based on type
const success = await this.executeOperation(operation, crdt);
if (success) {
// Log operation
this.operationLog.push(operation);
// Update vector clock
this.vectorClock.merge(operation.vectorClock);
this.emit("operation_applied", operation);
}
return success;
} catch (error) {
this.logger.error("Failed to apply operation", {
operation,
error: error.message,
});
return false;
}
}
/**
* Merge two CRDTs of the same type
*/
async merge(localValue: any, remoteValue: any): Promise<any> {
try {
// Determine CRDT types and merge accordingly
if (this.isGCounter(localValue) && this.isGCounter(remoteValue)) {
return localValue.merge(remoteValue);
}
if (this.isPNCounter(localValue) && this.isPNCounter(remoteValue)) {
return localValue.merge(remoteValue);
}
if (this.isORSet(localValue) && this.isORSet(remoteValue)) {
return localValue.merge(remoteValue);
}
if (this.isLWWRegister(localValue) && this.isLWWRegister(remoteValue)) {
return localValue.merge(remoteValue);
}
if (this.isMVRegister(localValue) && this.isMVRegister(remoteValue)) {
return localValue.merge(remoteValue);
}
if (this.isCRDTMap(localValue) && this.isCRDTMap(remoteValue)) {
return localValue.merge(remoteValue);
}
// Fallback: use LWW semantics for non-CRDT values
return this.lastWriterWinsMerge(localValue, remoteValue);
} catch (error) {
this.logger.error("CRDT merge failed", { error: error.message });
throw error;
}
}
/**
* Generate state vector for synchronization
*/
generateStateVector(): VectorClock {
return this.vectorClock.copy();
}
/**
* Get operations since a given state vector
*/
getOperationsSince(stateVector: VectorClock): CRDTOperation[] {
return this.operationLog.filter((op) => {
const comparison = op.vectorClock.compare(stateVector);
return comparison === "after" || comparison === "concurrent";
});
}
/**
* Synchronize with remote agent
*/
async synchronizeWith(
remoteAgentId: string,
remoteOperations: CRDTOperation[],
remoteStateVector: VectorClock,
): Promise<{
success: boolean;
appliedOperations: number;
conflicts: number;
newOperations: CRDTOperation[];
}> {
const startTime = Date.now();
let appliedOperations = 0;
let conflicts = 0;
try {
// Get our operations to send
const ourOperations = this.getOperationsSince(remoteStateVector);
// Apply remote operations
for (const operation of remoteOperations) {
const applied = await this.applyOperation(operation);
if (applied) {
appliedOperations++;
} else {
conflicts++;
this.emit("conflict_detected", {
operation,
remoteAgent: remoteAgentId,
reason: "operation_rejected",
});
}
}
// Update sync state
const syncState: SyncState = {
lastSyncVector: remoteStateVector.copy(),
pendingOperations: [],
conflictCount: conflicts,
mergeCount: appliedOperations,
lastSyncTime: new Date(),
};
this.syncStates.set(remoteAgentId, syncState);
const syncTime = Date.now() - startTime;
this.logger.info("Synchronization completed", {
remoteAgent: remoteAgentId,
applied: appliedOperations,
conflicts,
ourOperations: ourOperations.length,
syncTime,
});
this.emit("sync_completed", {
remoteAgent: remoteAgentId,
appliedOperations,
conflicts,
syncTime,
});
return {
success: true,
appliedOperations,
conflicts,
newOperations: ourOperations,
};
} catch (error) {
this.logger.error("Synchronization failed", {
remoteAgent: remoteAgentId,
error: error.message,
});
return {
success: false,
appliedOperations,
conflicts: conflicts + 1,
newOperations: [],
};
}
}
/**
* Get all CRDTs
*/
getAllCRDTs(): Map<string, CRDT> {
return new Map(this.crdts);
}
/**
* Get synchronization statistics
*/
getSyncStats(): {
totalCRDTs: number;
totalOperations: number;
syncStates: Map<string, SyncState>;
averageConflictRate: number;
} {
// Note: CRDTs don't require quorum as they achieve eventual consistency
// through commutative operations and deterministic conflict resolution
const totalConflicts = Array.from(this.syncStates.values()).reduce(
(sum, state) => sum + state.conflictCount,
0,
);
const totalMerges = Array.from(this.syncStates.values()).reduce(
(sum, state) => sum + state.mergeCount,
0,
);
const averageConflictRate =
totalMerges > 0 ? totalConflicts / totalMerges : 0;
return {
totalCRDTs: this.crdts.size,
totalOperations: this.operationLog.length,
syncStates: new Map(this.syncStates),
averageConflictRate,
};
}
/**
* CRDTs achieve eventual consistency without quorum requirements
* This method exists for API consistency but always returns true
*/
hasQuorum(): boolean {
// CRDTs don't require quorum - eventual consistency through convergent operations
return true;
}
/**
* Get quorum size - not applicable for CRDTs but provided for API consistency
*/
getMinQuorum(): number {
// CRDTs don't use quorum-based consensus
return 1;
}
/**
* Clean up old operations (garbage collection)
*/
garbageCollect(olderThan: Date): void {
const initialCount = this.operationLog.length;
this.operationLog = this.operationLog.filter(
(op) => op.timestamp > olderThan,
);
const cleaned = initialCount - this.operationLog.length;
if (cleaned > 0) {
this.logger.info("Operation log cleaned up", {
cleaned,
remaining: this.operationLog.length,
});
}
}
/**
* Private helper methods
*/
private shouldApplyOperation(operation: CRDTOperation, crdt: CRDT): boolean {
// Check if operation is newer than what we have
const comparison = operation.vectorClock.compare(crdt.vectorClock);
return comparison === "after" || comparison === "concurrent";
}
private async executeOperation(
operation: CRDTOperation,
crdt: CRDT,
): Promise<boolean> {
try {
switch (operation.type) {
case "increment":
if (crdt.type === "g-counter") {
(crdt as GCounter).increment(operation.agentId, operation.value);
return true;
} else if (crdt.type === "pn-counter") {
(crdt as PNCounter).increment(operation.agentId, operation.value);
return true;
}
break;
case "decrement":
if (crdt.type === "pn-counter") {
(crdt as PNCounter).decrement(operation.agentId, operation.value);
return true;
}
break;
case "add":
if (crdt.type === "or-set") {
(crdt as ORSet).add(operation.value, operation.agentId);
return true;
}
break;
case "remove":
if (crdt.type === "or-set") {
(crdt as ORSet).remove(operation.value);
return true;
}
break;
case "set":
if (crdt.type === "lww-register") {
(crdt as LWWRegister).set(operation.value, operation.agentId);
return true;
} else if (crdt.type === "mv-register") {
(crdt as MVRegister).set(operation.value, operation.agentId);
return true;
}
break;
default:
this.logger.warn("Unknown operation type", { type: operation.type });
return false;
}
return false;
} catch (error) {
this.logger.error("Operation execution failed", {
operation: operation.type,
crdtType: crdt.type,
error: error.message,
});
return false;
}
}
private lastWriterWinsMerge(local: any, remote: any): any {
// Simple LWW merge for non-CRDT values
// In practice, you'd want more sophisticated conflict resolution
const localTime = local?.timestamp || 0;
const remoteTime = remote?.timestamp || 0;
return localTime >= remoteTime ? local : remote;
}
// Type guards
private isGCounter(obj: any): obj is GCounter {
return obj && obj.type === "g-counter";
}
private isPNCounter(obj: any): obj is PNCounter {
return obj && obj.type === "pn-counter";
}
private isORSet(obj: any): obj is ORSet {
return obj && obj.type === "or-set";
}
private isLWWRegister(obj: any): obj is LWWRegister {
return obj && obj.type === "lww-register";
}
private isMVRegister(obj: any): obj is MVRegister {
return obj && obj.type === "mv-register";
}
private isCRDTMap(obj: any): obj is CRDTMap {
return obj && obj.type === "crdt-map";
}
}
// Export CRDT classes for direct use
export { GCounter, PNCounter, ORSet, LWWRegister, MVRegister, CRDTMap };