UNPKG

agentdb

Version:

AgentDB - Frontier Memory Features with MCP Integration and Direct Vector Search: Causal reasoning, reflexion memory, skill library, automated learning, and raw vector similarity queries. 150x faster vector search. Full Claude Desktop support via Model Co

773 lines (668 loc) 19.2 kB
/** * QUIC Synchronization Types for AgentDB * * This file contains TypeScript interfaces and types for the QUIC-based * multi-node synchronization system. These types mirror the Protocol Buffer * definitions and provide type safety for the sync implementation. */ // ============================================================================ // Core Sync Types // ============================================================================ /** * Vector clock for causal ordering of events across distributed nodes. * Maps node IDs to their logical clock values. */ export interface VectorClock { clocks: Map<string, number>; // node_id -> logical_clock } /** * Compares two vector clocks to determine causal relationship */ export type VectorClockComparison = | 'before' // local happened before remote | 'after' // local happened after remote | 'concurrent' // concurrent events (conflict) | 'equal'; // identical clocks /** * Main sync message envelope wrapping all sync operations */ export interface SyncMessage { sequenceNumber: number; timestampMs: number; nodeId: string; vectorClock: VectorClock; payload: SyncPayload; } /** * Union type for different sync payload types */ export type SyncPayload = | { type: 'episode_sync'; data: EpisodeSync } | { type: 'skill_sync'; data: SkillSync } | { type: 'causal_edge_sync'; data: CausalEdgeSync } | { type: 'reconciliation_request'; data: FullReconciliationRequest } | { type: 'reconciliation_response'; data: FullReconciliationResponse }; // ============================================================================ // Episode Synchronization // ============================================================================ /** * Supported operations for episode sync */ export enum EpisodeSyncOperation { CREATE = 'CREATE', UPDATE = 'UPDATE', DELETE = 'DELETE' } /** * Episode synchronization message */ export interface EpisodeSync { operation: EpisodeSyncOperation; episodeId: number; episodeData: SyncableEpisode; causalClock: VectorClock; signature: Uint8Array; // HMAC for integrity verification } /** * Serializable episode data for sync */ export interface SyncableEpisode { id?: number; agentId: string; sessionId: string; task: string; input: string; output: string; critique?: string; reward: number; success: boolean; latencyMs: number; timestamp: number; metadata?: Record<string, any>; vectorClock: VectorClock; } // ============================================================================ // Skill Synchronization (CRDT-based) // ============================================================================ /** * G-Counter (Grow-only Counter) for skill usage tracking */ export interface GCounter { nodeCounters: Map<string, number>; // node_id -> local_count } /** * LWW-Register (Last-Write-Wins Register) for scalar values */ export interface LWWRegister<T> { value: T; timestamp: number; nodeId: string; } /** * OR-Set (Observed-Remove Set) for set-based values */ export interface ORSet<T> { adds: Map<T, Set<string>>; // element -> set of unique tags removes: Set<string>; // set of removed tags } /** * Skill synchronization message with CRDT fields */ export interface SkillSync { skillId: number; skillName: string; description?: string; // CRDT fields uses: GCounter; // Total uses across nodes successRate: LWWRegister<number>; // Success rate with timestamp avgReward: LWWRegister<number>; // Average reward with timestamp avgLatencyMs: LWWRegister<number>; // Average latency with timestamp sourceEpisodes: ORSet<number>; // Set of source episode IDs // Metadata signature: Record<string, any>; // Skill signature (inputs/outputs) version: VectorClock; metadata?: Record<string, any>; } // ============================================================================ // Causal Edge Synchronization // ============================================================================ /** * Metadata for conflict resolution in causal edges */ export interface ConflictResolutionMetadata { experimentIds?: number[]; evidenceCount: number; lastModifiedBy: string; lastModifiedAt: number; } /** * Causal edge synchronization message */ export interface CausalEdgeSync { edgeId: number; fromMemoryId: number; fromMemoryType: 'episode' | 'skill' | 'note' | 'fact'; toMemoryId: number; toMemoryType: 'episode' | 'skill' | 'note' | 'fact'; // Causal metrics similarity: number; uplift?: number; confidence: number; sampleSize?: number; // Evidence and explanation evidenceIds?: number[]; experimentIds?: number[]; confounderScore?: number; mechanism?: string; // Sync metadata version: VectorClock; conflictMetadata: ConflictResolutionMetadata; metadata?: Record<string, any>; } // ============================================================================ // Full Reconciliation // ============================================================================ /** * Data types that can be reconciled */ export type ReconciliableDataType = 'episodes' | 'skills' | 'edges' | 'experiments'; /** * Request for full reconciliation */ export interface FullReconciliationRequest { lastSyncTimestamp: number; currentState: VectorClock; dataTypes: ReconciliableDataType[]; requestId: string; } /** * Response with full state for reconciliation */ export interface FullReconciliationResponse { requestId: string; episodes: EpisodeSync[]; skills: SkillSync[]; edges: CausalEdgeSync[]; authoritativeClock: VectorClock; merkleRoot: string; // For verification } /** * State summary for efficient reconciliation */ export interface StateSummary { episodes: { count: number; merkleRoot: string; vectorClock: VectorClock; }; skills: { count: number; merkleRoot: string; vectorClock: VectorClock; }; edges: { count: number; merkleRoot: string; vectorClock: VectorClock; }; } /** * Reconciliation report with results */ export interface ReconciliationReport { success: boolean; startTime: number; endTime: number; duration: number; recordsAdded: number; recordsUpdated: number; recordsDeleted: number; conflictsResolved: number; conflictsUnresolved: number; errors: string[]; } // ============================================================================ // Authentication & Authorization // ============================================================================ /** * JWT claims for API authorization */ export interface JWTClaims { iss: string; // Issuer sub: string; // Subject (node ID) exp: number; // Expiration timestamp iat: number; // Issued at timestamp roles: UserRole[]; scopes: AuthScope[]; networkId: string; metadata?: Record<string, any>; } /** * User roles */ export enum UserRole { ADMIN = 'admin', AGENT = 'agent', OBSERVER = 'observer', LEARNER = 'learner' } /** * Authorization scopes */ export type AuthScope = | 'episodes:read' | 'episodes:write' | 'episodes:delete' | 'skills:read' | 'skills:write' | 'skills:delete' | 'edges:read' | 'edges:write' | 'edges:delete' | 'experiments:read' | 'experiments:write' | 'reconciliation:request'; /** * Node registration data */ export interface NodeRegistration { nodeId: string; certificate: string; // PEM-encoded X.509 certificate publicKey: string; // PEM-encoded public key networkId: string; registeredAt: number; expiresAt: number; } // ============================================================================ // Configuration // ============================================================================ /** * Network topology types */ export enum NetworkTopology { HUB_AND_SPOKE = 'hub_and_spoke', MESH = 'mesh', HIERARCHICAL = 'hierarchical' } /** * Conflict resolution strategies */ export enum ConflictResolutionStrategy { AUTO = 'auto', // Automatic resolution using configured algorithms MANUAL = 'manual', // Flag conflicts for manual resolution INTERACTIVE = 'interactive' // Prompt user for resolution } /** * Sync mode */ export enum SyncMode { INCREMENTAL = 'incremental', FULL = 'full', HYBRID = 'hybrid' } /** * Server configuration */ export interface ServerConfig { port: number; host: string; maxConnections: number; maxStreamsPerConnection: number; // TLS/Security tlsCertPath: string; tlsKeyPath: string; caCertPath: string; jwtSecret: string; jwtExpirationMs: number; // Sync settings changelogRetentionDays: number; changelogMaxRecords: number; reconciliationIntervalMs: number; // Performance batchSize: number; compressionThreshold: number; maxMemoryPerConnection: number; // Topology topology: NetworkTopology; networkId: string; } /** * Client configuration */ export interface ClientConfig { nodeId: string; serverUrl: string; // TLS/Security clientCertPath: string; clientKeyPath: string; caCertPath: string; jwt: string; // Sync settings mode: SyncMode; incrementalIntervalMs: number; fullReconciliationIntervalMs: number; autoSync: boolean; // Conflict resolution conflictResolutionStrategy: ConflictResolutionStrategy; // Performance batchSize: number; compressionThreshold: number; retryMaxAttempts: number; retryBackoffMs: number; } // ============================================================================ // Status & Monitoring // ============================================================================ /** * Server status */ export interface ServerStatus { uptime: number; activeConnections: number; totalConnectionsHandled: number; activeStreams: number; changelogSize: number; lastReconciliation: number; // Performance metrics avgSyncLatencyMs: number; throughputBytesPerSec: number; conflictsPerMinute: number; // Resource usage cpuUsagePercent: number; memoryUsageBytes: number; diskUsageBytes: number; } /** * Client status */ export interface ClientStatus { connected: boolean; nodeId: string; serverUrl: string; lastSyncTimestamp: number; nextSyncScheduled: number; // Sync stats episodesSynced: number; skillsSynced: number; edgesSynced: number; conflictsEncountered: number; conflictsAutoResolved: number; // Connection health connectionUptimeMs: number; reconnectAttempts: number; lastError?: string; } /** * Sync result for a single operation */ export interface SyncResult { success: boolean; duration: number; // Changes applied episodesAdded: number; episodesUpdated: number; episodesDeleted: number; skillsAdded: number; skillsUpdated: number; edgesAdded: number; edgesUpdated: number; // Conflicts conflictsTotal: number; conflictsAutoResolved: number; conflictsPending: number; // Errors errors: SyncError[]; } /** * Sync error details */ export interface SyncError { code: string; message: string; dataType: ReconciliableDataType; recordId?: number; timestamp: number; retryable: boolean; } // ============================================================================ // Helper Functions (Type Guards & Utilities) // ============================================================================ /** * Type guard for episode sync */ export function isEpisodeSync(payload: SyncPayload): payload is { type: 'episode_sync'; data: EpisodeSync } { return payload.type === 'episode_sync'; } /** * Type guard for skill sync */ export function isSkillSync(payload: SyncPayload): payload is { type: 'skill_sync'; data: SkillSync } { return payload.type === 'skill_sync'; } /** * Type guard for causal edge sync */ export function isCausalEdgeSync(payload: SyncPayload): payload is { type: 'causal_edge_sync'; data: CausalEdgeSync } { return payload.type === 'causal_edge_sync'; } /** * Compare two vector clocks */ export function compareVectorClocks(a: VectorClock, b: VectorClock): VectorClockComparison { const allNodes = new Set([...a.clocks.keys(), ...b.clocks.keys()]); let aGreater = false; let bGreater = false; for (const node of allNodes) { const aClock = a.clocks.get(node) || 0; const bClock = b.clocks.get(node) || 0; if (aClock > bClock) aGreater = true; if (bClock > aClock) bGreater = true; } if (aGreater && !bGreater) return 'after'; if (bGreater && !aGreater) return 'before'; if (!aGreater && !bGreater) return 'equal'; return 'concurrent'; } /** * Merge two vector clocks (take max of each node) */ export function mergeVectorClocks(a: VectorClock, b: VectorClock): VectorClock { const merged = new Map(a.clocks); for (const [node, clock] of b.clocks) { const existingClock = merged.get(node) || 0; merged.set(node, Math.max(existingClock, clock)); } return { clocks: merged }; } /** * Increment vector clock for local node */ export function incrementVectorClock(clock: VectorClock, nodeId: string): VectorClock { const newClocks = new Map(clock.clocks); const currentClock = newClocks.get(nodeId) || 0; newClocks.set(nodeId, currentClock + 1); return { clocks: newClocks }; } /** * Create empty vector clock */ export function createVectorClock(): VectorClock { return { clocks: new Map() }; } // ============================================================================ // CRDT Operations // ============================================================================ /** * Increment G-Counter for a node */ export function incrementGCounter(counter: GCounter, nodeId: string, delta: number = 1): GCounter { const newCounters = new Map(counter.nodeCounters); const current = newCounters.get(nodeId) || 0; newCounters.set(nodeId, current + delta); return { nodeCounters: newCounters }; } /** * Get total value of G-Counter */ export function getGCounterValue(counter: GCounter): number { return Array.from(counter.nodeCounters.values()).reduce((sum, count) => sum + count, 0); } /** * Merge two G-Counters (take max per node) */ export function mergeGCounter(a: GCounter, b: GCounter): GCounter { const merged = new Map(a.nodeCounters); for (const [nodeId, count] of b.nodeCounters) { const existingCount = merged.get(nodeId) || 0; merged.set(nodeId, Math.max(existingCount, count)); } return { nodeCounters: merged }; } /** * Update LWW-Register with new value */ export function updateLWWRegister<T>( register: LWWRegister<T>, newValue: T, nodeId: string, timestamp: number = Date.now() ): LWWRegister<T> { if (timestamp > register.timestamp || (timestamp === register.timestamp && nodeId > register.nodeId)) { return { value: newValue, timestamp, nodeId }; } return register; } /** * Merge two LWW-Registers (keep most recent) */ export function mergeLWWRegister<T>(a: LWWRegister<T>, b: LWWRegister<T>): LWWRegister<T> { if (b.timestamp > a.timestamp) { return b; } else if (b.timestamp === a.timestamp) { return b.nodeId > a.nodeId ? b : a; } return a; } /** * Add element to OR-Set */ export function addToORSet<T>(set: ORSet<T>, element: T, uniqueTag: string): ORSet<T> { const newAdds = new Map(set.adds); if (!newAdds.has(element)) { newAdds.set(element, new Set()); } newAdds.get(element)!.add(uniqueTag); return { adds: newAdds, removes: set.removes }; } /** * Remove element from OR-Set */ export function removeFromORSet<T>(set: ORSet<T>, element: T): ORSet<T> { const tags = set.adds.get(element); if (!tags) return set; const newRemoves = new Set(set.removes); tags.forEach(tag => newRemoves.add(tag)); return { adds: set.adds, removes: newRemoves }; } /** * Get current elements in OR-Set */ export function getORSetElements<T>(set: ORSet<T>): Set<T> { const elements = new Set<T>(); for (const [element, tags] of set.adds) { // Check if any tag is not in removes for (const tag of tags) { if (!set.removes.has(tag)) { elements.add(element); break; } } } return elements; } /** * Merge two OR-Sets */ export function mergeORSet<T>(a: ORSet<T>, b: ORSet<T>): ORSet<T> { const mergedAdds = new Map<T, Set<string>>(); const mergedRemoves = new Set([...a.removes, ...b.removes]); // Merge adds from both sets const allElements = new Set([...a.adds.keys(), ...b.adds.keys()]); for (const element of allElements) { const aTags = a.adds.get(element) || new Set(); const bTags = b.adds.get(element) || new Set(); const mergedTags = new Set([...aTags, ...bTags]); // Remove tags that are in removes set for (const tag of mergedTags) { if (mergedRemoves.has(tag)) { mergedTags.delete(tag); } } if (mergedTags.size > 0) { mergedAdds.set(element, mergedTags); } } return { adds: mergedAdds, removes: mergedRemoves }; } // ============================================================================ // Conflict Resolution Helpers // ============================================================================ /** * Weighted average for numeric conflict resolution */ export function weightedAverage(v1: number, w1: number, v2: number, w2: number): number { if (w1 + w2 === 0) return 0; return (v1 * w1 + v2 * w2) / (w1 + w2); } /** * Determine authorization for operation */ export function isAuthorized(jwt: JWTClaims, requiredScope: AuthScope): boolean { return jwt.scopes.includes(requiredScope); } /** * Check if JWT is expired */ export function isJWTExpired(jwt: JWTClaims): boolean { return Date.now() >= jwt.exp * 1000; } /** * Generate unique tag for OR-Set operations */ export function generateUniqueTag(nodeId: string, timestamp: number = Date.now()): string { return `${nodeId}-${timestamp}-${Math.random().toString(36).substr(2, 9)}`; } // ============================================================================ // Event Types for Client SDK // ============================================================================ /** * Events emitted by sync client */ export type SyncEvent = | { type: 'sync_started'; timestamp: number } | { type: 'sync_completed'; result: SyncResult } | { type: 'sync_failed'; error: SyncError } | { type: 'conflict_detected'; conflict: ConflictData } | { type: 'conflict_resolved'; conflict: ConflictData; resolution: any } | { type: 'connection_established'; nodeId: string; serverUrl: string } | { type: 'connection_lost'; reason: string } | { type: 'reconnecting'; attempt: number } | { type: 'reconciliation_started'; requestId: string } | { type: 'reconciliation_completed'; report: ReconciliationReport }; /** * Conflict data for manual resolution */ export interface ConflictData { dataType: ReconciliableDataType; recordId: number; localVersion: any; remoteVersion: any; localVectorClock: VectorClock; remoteVectorClock: VectorClock; detectedAt: number; } /** * Event handler type */ export type SyncEventHandler = (event: SyncEvent) => void;