/** * Database queries for ReasoningBank * Operates on Claude Flow's memory.db at .swarm/memory.db */ import Database from 'better-sqlite3'; import { existsSync, mkdirSync } from 'fs'; import { join, dirname } from 'path'; // Simple logger for database operations const logger = { info: (msg, data) => console.log(`[INFO] ${msg}`, data || ''), error: (msg, data) => console.error(`[ERROR] ${msg}`, data || '') }; let dbInstance = null; /** * Run database migrations (create tables) */ export async function runMigrations() { const dbPath = process.env.CLAUDE_FLOW_DB_PATH || join(process.cwd(), '.swarm', 'memory.db'); // Create directory if it doesn't exist const dbDir = dirname(dbPath); if (!existsSync(dbDir)) { mkdirSync(dbDir, { recursive: true }); logger.info('Created database directory', { path: dbDir }); } // Create database file const db = new Database(dbPath); db.pragma('journal_mode = WAL'); db.pragma('foreign_keys = ON'); // Create tables db.exec(` CREATE TABLE IF NOT EXISTS patterns ( id TEXT PRIMARY KEY, type TEXT NOT NULL, pattern_data TEXT NOT NULL, confidence REAL NOT NULL DEFAULT 0.5, usage_count INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, last_used TEXT ); CREATE TABLE IF NOT EXISTS pattern_embeddings ( id TEXT PRIMARY KEY, model TEXT NOT NULL, dims INTEGER NOT NULL, vector BLOB NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (id) REFERENCES patterns(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS pattern_links ( src_id TEXT NOT NULL, dst_id TEXT NOT NULL, relation TEXT NOT NULL, weight REAL NOT NULL DEFAULT 1.0, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (src_id, dst_id, relation), FOREIGN KEY (src_id) REFERENCES patterns(id) ON DELETE CASCADE, FOREIGN KEY (dst_id) REFERENCES patterns(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS task_trajectories ( task_id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, query TEXT NOT NULL, trajectory_json TEXT NOT NULL, started_at TEXT, ended_at TEXT, judge_label TEXT, judge_conf REAL, judge_reasons TEXT, matts_run_id TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS matts_runs ( run_id TEXT PRIMARY KEY, task_id TEXT NOT NULL, mode TEXT NOT NULL, k INTEGER NOT NULL, status TEXT NOT NULL, summary TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS consolidation_runs ( run_id TEXT PRIMARY KEY, items_processed INTEGER NOT NULL, duplicates_found INTEGER NOT NULL, contradictions_found INTEGER NOT NULL, items_pruned INTEGER NOT NULL, duration_ms INTEGER NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS metrics_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, metric_name TEXT NOT NULL, value REAL NOT NULL, timestamp TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_patterns_type ON patterns(type); CREATE INDEX IF NOT EXISTS idx_patterns_confidence ON patterns(confidence DESC); CREATE INDEX IF NOT EXISTS idx_patterns_created_at ON patterns(created_at DESC); CREATE INDEX IF NOT EXISTS idx_pattern_links_relation ON pattern_links(relation); CREATE INDEX IF NOT EXISTS idx_trajectories_agent ON task_trajectories(agent_id); `); db.close(); dbInstance = null; // Reset instance to force reconnection logger.info('Database migrations completed', { path: dbPath }); } /** * Get database connection (singleton) */ export function getDb() { if (dbInstance) return dbInstance; const dbPath = process.env.CLAUDE_FLOW_DB_PATH || join(process.cwd(), '.swarm', 'memory.db'); if (!existsSync(dbPath)) { throw new Error(`Database not found at ${dbPath}. Run migrations first.`); } dbInstance = new Database(dbPath); dbInstance.pragma('journal_mode = WAL'); dbInstance.pragma('foreign_keys = ON'); logger.info('Connected to ReasoningBank database', { path: dbPath }); return dbInstance; } /** * Fetch reasoning memory candidates for retrieval */ export function fetchMemoryCandidates(options) { const db = getDb(); let query = ` SELECT p.*, pe.vector as embedding, CAST((julianday('now') - julianday(p.created_at)) AS INTEGER) as age_days FROM patterns p JOIN pattern_embeddings pe ON p.id = pe.id WHERE p.type = 'reasoning_memory' AND p.confidence >= ? `; const params = [options.minConfidence || 0.3]; if (options.domain) { query += ` AND json_extract(p.pattern_data, '$.domain') = ?`; params.push(options.domain); } query += ` ORDER BY p.confidence DESC, p.usage_count DESC`; const stmt = db.prepare(query); const rows = stmt.all(...params); return rows.map((row) => { const buffer = Buffer.from(row.embedding); // Create Float32Array from buffer - buffer length / 4 bytes per float const float32Array = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.length / 4); return { ...row, pattern_data: JSON.parse(row.pattern_data), embedding: float32Array }; }); } /** * Store a new reasoning memory */ export function upsertMemory(memory) { const db = getDb(); const stmt = db.prepare(` INSERT OR REPLACE INTO patterns (id, type, pattern_data, confidence, usage_count, created_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) `); stmt.run(memory.id, memory.type, JSON.stringify(memory.pattern_data), memory.confidence, memory.usage_count); logger.info('Upserted reasoning memory', { id: memory.id, title: memory.pattern_data.title }); return memory.id; } /** * Store embedding for a memory */ export function upsertEmbedding(embedding) { const db = getDb(); const buffer = Buffer.from(embedding.vector.buffer); const stmt = db.prepare(` INSERT OR REPLACE INTO pattern_embeddings (id, model, dims, vector, created_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) `); stmt.run(embedding.id, embedding.model, embedding.dims, buffer); } /** * Increment usage count for a memory */ export function incrementUsage(memoryId) { const db = getDb(); db.prepare(` UPDATE patterns SET usage_count = usage_count + 1, last_used = CURRENT_TIMESTAMP WHERE id = ? `).run(memoryId); } /** * Store task trajectory */ export function storeTrajectory(trajectory) { const db = getDb(); db.prepare(` INSERT OR REPLACE INTO task_trajectories (task_id, agent_id, query, trajectory_json, started_at, ended_at, judge_label, judge_conf, judge_reasons, matts_run_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) `).run(trajectory.task_id, trajectory.agent_id, trajectory.query, trajectory.trajectory_json, trajectory.started_at || null, trajectory.ended_at || null, trajectory.judge_label || null, trajectory.judge_conf || null, trajectory.judge_reasons || null, trajectory.matts_run_id || null); } /** * Store MaTTS run */ export function storeMattsRun(run) { const db = getDb(); db.prepare(` INSERT INTO matts_runs (run_id, task_id, mode, k, status, summary, created_at) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) `).run(run.run_id, run.task_id, run.mode, run.k, run.status, run.summary || null); } /** * Log performance metric */ export function logMetric(name, value) { try { const db = getDb(); // Log to metrics_log table (our own table) db.prepare(` INSERT INTO metrics_log (metric_name, value, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP) `).run(name, value); } catch (error) { // Silently fail if metrics table doesn't exist // This is optional logging, not critical } } /** * Count new memories since last consolidation */ export function countNewMemoriesSinceConsolidation() { const db = getDb(); const lastRun = db.prepare(` SELECT created_at FROM consolidation_runs ORDER BY created_at DESC LIMIT 1 `).get(); if (!lastRun) { // No consolidation yet, count all memories const result = db.prepare(` SELECT COUNT(*) as count FROM patterns WHERE type = 'reasoning_memory' `).get(); return result.count; } const result = db.prepare(` SELECT COUNT(*) as count FROM patterns WHERE type = 'reasoning_memory' AND created_at > ? `).get(lastRun.created_at); return result.count; } /** * Get all active reasoning memories */ export function getAllActiveMemories() { const db = getDb(); const rows = db.prepare(` SELECT * FROM patterns WHERE type = 'reasoning_memory' AND confidence >= 0.3 ORDER BY confidence DESC, usage_count DESC `).all(); return rows.map((row) => ({ ...row, pattern_data: JSON.parse(row.pattern_data) })); } /** * Store memory link (relationship) */ export function storeLink(srcId, dstId, relation, weight) { const db = getDb(); db.prepare(` INSERT OR REPLACE INTO pattern_links (src_id, dst_id, relation, weight, created_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) `).run(srcId, dstId, relation, weight); } /** * Get contradictions for a memory */ export function getContradictions(memoryId) { const db = getDb(); const rows = db.prepare(` SELECT dst_id FROM pattern_links WHERE src_id = ? AND relation = 'contradicts' `).all(memoryId); return rows.map(r => r.dst_id); } /** * Store consolidation run */ export function storeConsolidationRun(run) { const db = getDb(); db.prepare(` INSERT INTO consolidation_runs (run_id, items_processed, duplicates_found, contradictions_found, items_pruned, duration_ms, created_at) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) `).run(run.run_id, run.items_processed, run.duplicates_found, run.contradictions_found, run.items_pruned, run.duration_ms); } /** * Prune old, unused memories */ export function pruneOldMemories(options) { const db = getDb(); const result = db.prepare(` DELETE FROM patterns WHERE type = 'reasoning_memory' AND usage_count = 0 AND confidence < ? AND CAST((julianday('now') - julianday(created_at)) AS INTEGER) > ? `).run(options.minConfidence, options.maxAgeDays); return result.changes; } /** * Close database connection */ export function closeDb() { if (dbInstance) { dbInstance.close(); dbInstance = null; logger.info('Closed ReasoningBank database connection'); } } //# sourceMappingURL=queries.js.map