@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.
682 lines (565 loc) • 16.4 kB
text/typescript
/**
* Vector Clock Implementation for A2A Memory Coordination
*
* Provides logical timestamp management for distributed systems:
* - Causal ordering of events
* - Conflict detection in concurrent updates
* - Happens-before relationship tracking
* - Support for dynamic agent membership
* - Efficient serialization and deserialization
* - Clock pruning and garbage collection
*/
import { Logger } from "../../../utils/logger.js";
export type ClockComparison = "before" | "after" | "concurrent" | "equal";
export interface VectorClockState {
clocks: Map<string, number>;
agentId: string;
version: number;
lastUpdated: Date;
}
export interface ClockDelta {
agentId: string;
fromClock: number;
toClock: number;
timestamp: Date;
}
export interface ClockPruningConfig {
maxAge: number; // Maximum age in milliseconds
maxSize: number; // Maximum number of agent entries
pruneInterval: number; // Pruning interval in milliseconds
keepRecentAgents: number; // Always keep N most recent agents
}
/**
* Vector Clock implementation for distributed logical timestamps
*/
export class VectorClock {
private clocks: Map<string, number>;
private agentId: string;
private version: number;
private lastUpdated: Date;
private logger: Logger;
// Pruning configuration
private pruningConfig: ClockPruningConfig = {
maxAge: 24 * 60 * 60 * 1000, // 24 hours
maxSize: 1000,
pruneInterval: 60 * 60 * 1000, // 1 hour
keepRecentAgents: 50,
};
private agentLastSeen: Map<string, Date> = new Map();
private pruningTimer?: ReturnType<typeof setTimeout>;
constructor(agentId: string, initialClocks?: Map<string, number>) {
this.agentId = agentId;
this.clocks = new Map(initialClocks || [[agentId, 0]]);
this.version = 1;
this.lastUpdated = new Date();
this.logger = new Logger(`VectorClock:${agentId}`);
// Initialize agent last seen
this.agentLastSeen.set(agentId, new Date());
// Start pruning timer
this.startPruning();
this.logger.debug("Vector clock initialized", {
agentId,
initialSize: this.clocks.size,
});
}
/**
* Increment the local clock
*/
increment(): VectorClock {
const currentClock = this.clocks.get(this.agentId) || 0;
this.clocks.set(this.agentId, currentClock + 1);
this.version++;
this.lastUpdated = new Date();
this.agentLastSeen.set(this.agentId, new Date());
this.logger.trace("Clock incremented", {
agentId: this.agentId,
newClock: currentClock + 1,
version: this.version,
});
return this;
}
/**
* Update clock for a specific agent
*/
update(agentId: string, clockValue: number): VectorClock {
const currentClock = this.clocks.get(agentId) || 0;
if (clockValue > currentClock) {
this.clocks.set(agentId, clockValue);
this.version++;
this.lastUpdated = new Date();
this.agentLastSeen.set(agentId, new Date());
this.logger.trace("Clock updated", {
agentId,
fromClock: currentClock,
toClock: clockValue,
version: this.version,
});
}
return this;
}
/**
* Merge with another vector clock
*/
merge(other: VectorClock): VectorClock {
const merged = this.copy();
let hasChanges = false;
// Merge all clocks from other
for (const [agentId, clockValue] of other.clocks) {
const currentClock = merged.clocks.get(agentId) || 0;
if (clockValue > currentClock) {
merged.clocks.set(agentId, clockValue);
merged.agentLastSeen.set(agentId, new Date());
hasChanges = true;
}
}
// Increment our own clock
merged.increment();
if (hasChanges) {
merged.version++;
merged.lastUpdated = new Date();
this.logger.debug("Vector clocks merged", {
ourSize: this.clocks.size,
otherSize: other.clocks.size,
mergedSize: merged.clocks.size,
hasChanges,
});
}
return merged;
}
/**
* Compare this vector clock with another
*/
compare(other: VectorClock): ClockComparison {
if (this.equals(other)) {
return "equal";
}
let thisSmaller = false;
let thisLarger = false;
// Get all unique agent IDs
const allAgents = new Set([...this.clocks.keys(), ...other.clocks.keys()]);
for (const agentId of allAgents) {
const thisClock = this.clocks.get(agentId) || 0;
const otherClock = other.clocks.get(agentId) || 0;
if (thisClock < otherClock) {
thisSmaller = true;
} else if (thisClock > otherClock) {
thisLarger = true;
}
// If both conditions are true, clocks are concurrent
if (thisSmaller && thisLarger) {
return "concurrent";
}
}
if (thisSmaller) {
return "before";
} else if (thisLarger) {
return "after";
} else {
return "equal";
}
}
/**
* Check if this clock is newer than another
*/
isNewer(other: VectorClock): boolean {
const comparison = this.compare(other);
return comparison === "after";
}
/**
* Check if this clock is older than another
*/
isOlder(other: VectorClock): boolean {
const comparison = this.compare(other);
return comparison === "before";
}
/**
* Check if clocks are concurrent (incomparable)
*/
isConcurrent(other: VectorClock): boolean {
return this.compare(other) === "concurrent";
}
/**
* Check if clocks are equal
*/
equals(other: VectorClock): boolean {
if (this.clocks.size !== other.clocks.size) {
return false;
}
for (const [agentId, clockValue] of this.clocks) {
const otherClock = other.clocks.get(agentId) || 0;
if (clockValue !== otherClock) {
return false;
}
}
return true;
}
/**
* Get clock value for specific agent
*/
getClock(agentId: string): number {
return this.clocks.get(agentId) || 0;
}
/**
* Set clock value for specific agent
*/
setClock(agentId: string, clockValue: number): VectorClock {
this.clocks.set(agentId, clockValue);
this.agentLastSeen.set(agentId, new Date());
this.version++;
this.lastUpdated = new Date();
return this;
}
/**
* Get all agent IDs in this clock
*/
getAgents(): string[] {
return Array.from(this.clocks.keys());
}
/**
* Get clock size (number of agents)
*/
size(): number {
return this.clocks.size;
}
/**
* Create a copy of this vector clock
*/
copy(): VectorClock {
const copy = new VectorClock(this.agentId, new Map(this.clocks));
copy.version = this.version;
copy.lastUpdated = new Date(this.lastUpdated);
copy.agentLastSeen = new Map(this.agentLastSeen);
copy.pruningConfig = { ...this.pruningConfig };
return copy;
}
/**
* Calculate the delta between this clock and another
*/
delta(other: VectorClock): ClockDelta[] {
const deltas: ClockDelta[] = [];
// Check our clocks vs other's
for (const [agentId, clockValue] of this.clocks) {
const otherClock = other.clocks.get(agentId) || 0;
if (clockValue > otherClock) {
deltas.push({
agentId,
fromClock: otherClock,
toClock: clockValue,
timestamp: new Date(),
});
}
}
return deltas;
}
/**
* Apply deltas to this clock
*/
applyDeltas(deltas: ClockDelta[]): VectorClock {
let hasChanges = false;
for (const delta of deltas) {
const currentClock = this.clocks.get(delta.agentId) || 0;
if (delta.toClock > currentClock) {
this.clocks.set(delta.agentId, delta.toClock);
this.agentLastSeen.set(delta.agentId, delta.timestamp);
hasChanges = true;
}
}
if (hasChanges) {
this.version++;
this.lastUpdated = new Date();
this.logger.debug("Deltas applied", {
deltaCount: deltas.length,
clockSize: this.clocks.size,
});
}
return this;
}
/**
* Serialize to string representation
*/
toString(): string {
const clockArray = Array.from(this.clocks.entries()).sort(([a], [b]) =>
a.localeCompare(b),
);
return JSON.stringify({
clocks: clockArray,
agentId: this.agentId,
version: this.version,
lastUpdated: this.lastUpdated.toISOString(),
});
}
/**
* Deserialize from string representation
*/
static fromString(clockString: string): VectorClock {
try {
const data = JSON.parse(clockString);
const clocks = new Map(data.clocks);
const vectorClock = new VectorClock(data.agentId, clocks);
vectorClock.version = data.version || 1;
vectorClock.lastUpdated = new Date(data.lastUpdated);
return vectorClock;
} catch (error) {
throw new Error(`Failed to deserialize vector clock: ${error.message}`);
}
}
/**
* Serialize to compact binary format
*/
toBinary(): Buffer {
const clockEntries = Array.from(this.clocks.entries());
const buffer = Buffer.alloc(
4 + clockEntries.length * 12 + this.agentId.length,
);
let offset = 0;
// Write number of entries
buffer.writeUInt32BE(clockEntries.length, offset);
offset += 4;
// Write clock entries (agentId hash + clock value)
for (const [agentId, clockValue] of clockEntries) {
const agentHash = this.hashString(agentId);
buffer.writeUInt32BE(agentHash, offset);
buffer.writeUInt32BE(clockValue, offset + 4);
offset += 8;
}
return buffer;
}
/**
* Deserialize from binary format
*/
static fromBinary(buffer: Buffer): VectorClock {
let offset = 0;
// Read number of entries
const entryCount = buffer.readUInt32BE(offset);
offset += 4;
const clocks = new Map<string, number>();
// Read clock entries
for (let i = 0; i < entryCount; i++) {
const agentHash = buffer.readUInt32BE(offset);
const clockValue = buffer.readUInt32BE(offset + 4);
offset += 8;
// Note: We lose the original agent ID in binary format
// This is a trade-off for compactness
clocks.set(agentHash.toString(), clockValue);
}
// Use first entry as agent ID (approximation)
const agentId = clocks.keys().next().value || "unknown";
return new VectorClock(agentId, clocks);
}
/**
* Get state snapshot
*/
getState(): VectorClockState {
return {
clocks: new Map(this.clocks),
agentId: this.agentId,
version: this.version,
lastUpdated: new Date(this.lastUpdated),
};
}
/**
* Restore from state snapshot
*/
restoreState(state: VectorClockState): VectorClock {
this.clocks = new Map(state.clocks);
this.version = state.version;
this.lastUpdated = new Date(state.lastUpdated);
// Update agent last seen
for (const agentId of this.clocks.keys()) {
if (!this.agentLastSeen.has(agentId)) {
this.agentLastSeen.set(agentId, new Date());
}
}
this.logger.debug("State restored", {
clockSize: this.clocks.size,
version: this.version,
});
return this;
}
/**
* Configure pruning settings
*/
configurePruning(config: Partial<ClockPruningConfig>): void {
this.pruningConfig = { ...this.pruningConfig, ...config };
// Restart pruning with new config
if (this.pruningTimer) {
clearInterval(this.pruningTimer);
}
this.startPruning();
this.logger.info("Pruning configuration updated", this.pruningConfig);
}
/**
* Manual pruning of old entries
*/
prune(): number {
const now = new Date();
const cutoffTime = new Date(now.getTime() - this.pruningConfig.maxAge);
let prunedCount = 0;
const agentsToRemove: string[] = [];
// Find agents to remove based on age
for (const [agentId, lastSeen] of this.agentLastSeen) {
if (agentId !== this.agentId && lastSeen < cutoffTime) {
agentsToRemove.push(agentId);
}
}
// If still too many, remove oldest agents (except recent ones)
if (this.clocks.size > this.pruningConfig.maxSize) {
const sortedAgents = Array.from(this.agentLastSeen.entries())
.filter(([agentId]) => agentId !== this.agentId)
.sort(([, a], [, b]) => b.getTime() - a.getTime())
.slice(this.pruningConfig.keepRecentAgents)
.map(([agentId]) => agentId);
agentsToRemove.push(...sortedAgents);
}
// Remove agents
for (const agentId of agentsToRemove) {
if (this.clocks.delete(agentId)) {
this.agentLastSeen.delete(agentId);
prunedCount++;
}
}
if (prunedCount > 0) {
this.version++;
this.lastUpdated = new Date();
this.logger.info("Clock pruned", {
prunedCount,
remainingSize: this.clocks.size,
});
}
return prunedCount;
}
/**
* Get pruning statistics
*/
getPruningStats(): {
totalAgents: number;
oldestAgent: Date | null;
newestAgent: Date | null;
nextPruning: Date | null;
config: ClockPruningConfig;
} {
const lastSeenTimes = Array.from(this.agentLastSeen.values());
return {
totalAgents: this.clocks.size,
oldestAgent:
lastSeenTimes.length > 0
? new Date(Math.min(...lastSeenTimes.map((d) => d.getTime())))
: null,
newestAgent:
lastSeenTimes.length > 0
? new Date(Math.max(...lastSeenTimes.map((d) => d.getTime())))
: null,
nextPruning: this.pruningTimer
? new Date(Date.now() + this.pruningConfig.pruneInterval)
: null,
config: { ...this.pruningConfig },
};
}
/**
* Destroy and cleanup resources
*/
destroy(): void {
if (this.pruningTimer) {
clearInterval(this.pruningTimer);
this.pruningTimer = undefined;
}
this.clocks.clear();
this.agentLastSeen.clear();
this.logger.debug("Vector clock destroyed");
}
/**
* Private methods
*/
private startPruning(): void {
if (this.pruningConfig.pruneInterval > 0) {
this.pruningTimer = setInterval(() => {
this.prune();
}, this.pruningConfig.pruneInterval);
}
}
private hashString(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash);
}
}
/**
* Vector Clock Manager for handling multiple clocks
*/
export class VectorClockManager {
private clocks: Map<string, VectorClock> = new Map();
private logger: Logger;
constructor() {
this.logger = new Logger("VectorClockManager");
}
/**
* Create or get a vector clock for an agent
*/
getOrCreateClock(agentId: string): VectorClock {
let clock = this.clocks.get(agentId);
if (!clock) {
clock = new VectorClock(agentId);
this.clocks.set(agentId, clock);
this.logger.debug("New vector clock created", { agentId });
}
return clock;
}
/**
* Remove clock for an agent
*/
removeClock(agentId: string): boolean {
const clock = this.clocks.get(agentId);
if (clock) {
clock.destroy();
this.clocks.delete(agentId);
this.logger.debug("Vector clock removed", { agentId });
return true;
}
return false;
}
/**
* Synchronize clocks between agents
*/
synchronizeClocks(
agentId1: string,
agentId2: string,
): {
clock1: VectorClock;
clock2: VectorClock;
synchronized: boolean;
} {
const clock1 = this.getOrCreateClock(agentId1);
const clock2 = this.getOrCreateClock(agentId2);
const mergedClock1 = clock1.merge(clock2);
const mergedClock2 = clock2.merge(clock1);
// Update stored clocks
this.clocks.set(agentId1, mergedClock1);
this.clocks.set(agentId2, mergedClock2);
this.logger.debug("Clocks synchronized", { agentId1, agentId2 });
return {
clock1: mergedClock1,
clock2: mergedClock2,
synchronized: true,
};
}
/**
* Get all managed clocks
*/
getAllClocks(): Map<string, VectorClock> {
return new Map(this.clocks);
}
/**
* Cleanup all clocks
*/
cleanup(): void {
for (const clock of this.clocks.values()) {
clock.destroy();
}
this.clocks.clear();
this.logger.info("All vector clocks cleaned up");
}
}