364 lines
11 KiB
JavaScript
364 lines
11 KiB
JavaScript
/**
|
|
* IntelligenceStore - SQLite persistence for RuVector intelligence layer
|
|
*
|
|
* Cross-platform (Linux, macOS, Windows) persistent storage for:
|
|
* - Learning trajectories
|
|
* - Routing patterns
|
|
* - SONA adaptations
|
|
* - HNSW vectors
|
|
*/
|
|
import Database from 'better-sqlite3';
|
|
import { existsSync, mkdirSync } from 'fs';
|
|
import { dirname, join } from 'path';
|
|
import { homedir } from 'os';
|
|
export class IntelligenceStore {
|
|
db;
|
|
static instance = null;
|
|
constructor(dbPath) {
|
|
// Ensure directory exists
|
|
const dir = dirname(dbPath);
|
|
if (!existsSync(dir)) {
|
|
mkdirSync(dir, { recursive: true });
|
|
}
|
|
this.db = new Database(dbPath);
|
|
this.db.pragma('journal_mode = WAL'); // Better concurrent access
|
|
this.db.pragma('synchronous = NORMAL'); // Good balance of speed/safety
|
|
this.initSchema();
|
|
}
|
|
/**
|
|
* Get singleton instance
|
|
*/
|
|
static getInstance(dbPath) {
|
|
if (!IntelligenceStore.instance) {
|
|
const path = dbPath || IntelligenceStore.getDefaultPath();
|
|
IntelligenceStore.instance = new IntelligenceStore(path);
|
|
}
|
|
return IntelligenceStore.instance;
|
|
}
|
|
/**
|
|
* Get default database path (cross-platform)
|
|
*/
|
|
static getDefaultPath() {
|
|
// Check for project-local .agentic-flow directory first
|
|
const localPath = join(process.cwd(), '.agentic-flow', 'intelligence.db');
|
|
const localDir = dirname(localPath);
|
|
if (existsSync(localDir)) {
|
|
return localPath;
|
|
}
|
|
// Fall back to home directory
|
|
const homeDir = homedir();
|
|
return join(homeDir, '.agentic-flow', 'intelligence.db');
|
|
}
|
|
/**
|
|
* Initialize database schema
|
|
*/
|
|
initSchema() {
|
|
this.db.exec(`
|
|
-- Trajectories table
|
|
CREATE TABLE IF NOT EXISTS trajectories (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
task_description TEXT NOT NULL,
|
|
agent TEXT NOT NULL,
|
|
steps INTEGER DEFAULT 0,
|
|
outcome TEXT DEFAULT 'partial',
|
|
start_time INTEGER NOT NULL,
|
|
end_time INTEGER,
|
|
metadata TEXT,
|
|
created_at INTEGER DEFAULT (strftime('%s', 'now'))
|
|
);
|
|
|
|
-- Patterns table (for ReasoningBank)
|
|
CREATE TABLE IF NOT EXISTS patterns (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
task_type TEXT NOT NULL,
|
|
approach TEXT NOT NULL,
|
|
embedding BLOB,
|
|
similarity REAL DEFAULT 0,
|
|
usage_count INTEGER DEFAULT 0,
|
|
success_rate REAL DEFAULT 0,
|
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
|
|
);
|
|
|
|
-- Routings table
|
|
CREATE TABLE IF NOT EXISTS routings (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
task TEXT NOT NULL,
|
|
recommended_agent TEXT NOT NULL,
|
|
confidence REAL NOT NULL,
|
|
latency_ms INTEGER NOT NULL,
|
|
was_successful INTEGER DEFAULT 0,
|
|
timestamp INTEGER DEFAULT (strftime('%s', 'now'))
|
|
);
|
|
|
|
-- Stats table (single row)
|
|
CREATE TABLE IF NOT EXISTS stats (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
total_trajectories INTEGER DEFAULT 0,
|
|
successful_trajectories INTEGER DEFAULT 0,
|
|
total_routings INTEGER DEFAULT 0,
|
|
successful_routings INTEGER DEFAULT 0,
|
|
total_patterns INTEGER DEFAULT 0,
|
|
sona_adaptations INTEGER DEFAULT 0,
|
|
hnsw_queries INTEGER DEFAULT 0,
|
|
last_updated INTEGER DEFAULT (strftime('%s', 'now'))
|
|
);
|
|
|
|
-- Initialize stats row if not exists
|
|
INSERT OR IGNORE INTO stats (id) VALUES (1);
|
|
|
|
-- Indexes for faster queries
|
|
CREATE INDEX IF NOT EXISTS idx_trajectories_agent ON trajectories(agent);
|
|
CREATE INDEX IF NOT EXISTS idx_trajectories_outcome ON trajectories(outcome);
|
|
CREATE INDEX IF NOT EXISTS idx_patterns_task_type ON patterns(task_type);
|
|
CREATE INDEX IF NOT EXISTS idx_routings_agent ON routings(recommended_agent);
|
|
CREATE INDEX IF NOT EXISTS idx_routings_timestamp ON routings(timestamp);
|
|
`);
|
|
}
|
|
// ============ Trajectory Methods ============
|
|
/**
|
|
* Start a new trajectory
|
|
*/
|
|
startTrajectory(taskDescription, agent) {
|
|
const stmt = this.db.prepare(`
|
|
INSERT INTO trajectories (task_description, agent, start_time)
|
|
VALUES (?, ?, ?)
|
|
`);
|
|
const result = stmt.run(taskDescription, agent, Date.now());
|
|
this.incrementStat('total_trajectories');
|
|
return result.lastInsertRowid;
|
|
}
|
|
/**
|
|
* Add step to trajectory
|
|
*/
|
|
addTrajectoryStep(trajectoryId) {
|
|
const stmt = this.db.prepare(`
|
|
UPDATE trajectories SET steps = steps + 1 WHERE id = ?
|
|
`);
|
|
stmt.run(trajectoryId);
|
|
}
|
|
/**
|
|
* End trajectory with outcome
|
|
*/
|
|
endTrajectory(trajectoryId, outcome, metadata) {
|
|
const stmt = this.db.prepare(`
|
|
UPDATE trajectories
|
|
SET outcome = ?, end_time = ?, metadata = ?
|
|
WHERE id = ?
|
|
`);
|
|
stmt.run(outcome, Date.now(), metadata ? JSON.stringify(metadata) : null, trajectoryId);
|
|
if (outcome === 'success') {
|
|
this.incrementStat('successful_trajectories');
|
|
}
|
|
}
|
|
/**
|
|
* Get active trajectories (no end_time)
|
|
*/
|
|
getActiveTrajectories() {
|
|
const stmt = this.db.prepare(`
|
|
SELECT * FROM trajectories WHERE end_time IS NULL
|
|
`);
|
|
return stmt.all();
|
|
}
|
|
/**
|
|
* Get recent trajectories
|
|
*/
|
|
getRecentTrajectories(limit = 10) {
|
|
const stmt = this.db.prepare(`
|
|
SELECT * FROM trajectories ORDER BY start_time DESC LIMIT ?
|
|
`);
|
|
return stmt.all(limit);
|
|
}
|
|
// ============ Pattern Methods ============
|
|
/**
|
|
* Store a pattern
|
|
*/
|
|
storePattern(taskType, approach, embedding) {
|
|
const stmt = this.db.prepare(`
|
|
INSERT INTO patterns (task_type, approach, embedding)
|
|
VALUES (?, ?, ?)
|
|
`);
|
|
const embeddingBuffer = embedding ? Buffer.from(embedding.buffer) : null;
|
|
const result = stmt.run(taskType, approach, embeddingBuffer);
|
|
this.incrementStat('total_patterns');
|
|
return result.lastInsertRowid;
|
|
}
|
|
/**
|
|
* Update pattern usage
|
|
*/
|
|
updatePatternUsage(patternId, wasSuccessful) {
|
|
const stmt = this.db.prepare(`
|
|
UPDATE patterns
|
|
SET usage_count = usage_count + 1,
|
|
success_rate = (success_rate * usage_count + ?) / (usage_count + 1),
|
|
updated_at = strftime('%s', 'now')
|
|
WHERE id = ?
|
|
`);
|
|
stmt.run(wasSuccessful ? 1 : 0, patternId);
|
|
}
|
|
/**
|
|
* Find patterns by task type
|
|
*/
|
|
findPatterns(taskType, limit = 5) {
|
|
const stmt = this.db.prepare(`
|
|
SELECT * FROM patterns
|
|
WHERE task_type LIKE ?
|
|
ORDER BY success_rate DESC, usage_count DESC
|
|
LIMIT ?
|
|
`);
|
|
return stmt.all(`%${taskType}%`, limit);
|
|
}
|
|
// ============ Routing Methods ============
|
|
/**
|
|
* Record a routing decision
|
|
*/
|
|
recordRouting(task, recommendedAgent, confidence, latencyMs) {
|
|
const stmt = this.db.prepare(`
|
|
INSERT INTO routings (task, recommended_agent, confidence, latency_ms)
|
|
VALUES (?, ?, ?, ?)
|
|
`);
|
|
const result = stmt.run(task, recommendedAgent, confidence, latencyMs);
|
|
this.incrementStat('total_routings');
|
|
return result.lastInsertRowid;
|
|
}
|
|
/**
|
|
* Update routing outcome
|
|
*/
|
|
updateRoutingOutcome(routingId, wasSuccessful) {
|
|
const stmt = this.db.prepare(`
|
|
UPDATE routings SET was_successful = ? WHERE id = ?
|
|
`);
|
|
stmt.run(wasSuccessful ? 1 : 0, routingId);
|
|
if (wasSuccessful) {
|
|
this.incrementStat('successful_routings');
|
|
}
|
|
}
|
|
/**
|
|
* Get routing accuracy for an agent
|
|
*/
|
|
getAgentAccuracy(agent) {
|
|
const stmt = this.db.prepare(`
|
|
SELECT
|
|
COUNT(*) as total,
|
|
SUM(was_successful) as successful
|
|
FROM routings
|
|
WHERE recommended_agent = ?
|
|
`);
|
|
const result = stmt.get(agent);
|
|
return {
|
|
total: result.total || 0,
|
|
successful: result.successful || 0,
|
|
accuracy: result.total > 0 ? (result.successful || 0) / result.total : 0,
|
|
};
|
|
}
|
|
// ============ Stats Methods ============
|
|
/**
|
|
* Get all stats
|
|
*/
|
|
getStats() {
|
|
const stmt = this.db.prepare(`SELECT * FROM stats WHERE id = 1`);
|
|
const row = stmt.get();
|
|
return {
|
|
totalTrajectories: row?.total_trajectories || 0,
|
|
successfulTrajectories: row?.successful_trajectories || 0,
|
|
totalRoutings: row?.total_routings || 0,
|
|
successfulRoutings: row?.successful_routings || 0,
|
|
totalPatterns: row?.total_patterns || 0,
|
|
sonaAdaptations: row?.sona_adaptations || 0,
|
|
hnswQueries: row?.hnsw_queries || 0,
|
|
lastUpdated: row?.last_updated || Date.now(),
|
|
};
|
|
}
|
|
/**
|
|
* Increment a stat counter
|
|
*/
|
|
incrementStat(statName, amount = 1) {
|
|
const stmt = this.db.prepare(`
|
|
UPDATE stats SET ${statName} = ${statName} + ?, last_updated = strftime('%s', 'now')
|
|
WHERE id = 1
|
|
`);
|
|
stmt.run(amount);
|
|
}
|
|
/**
|
|
* Record SONA adaptation
|
|
*/
|
|
recordSonaAdaptation() {
|
|
this.incrementStat('sona_adaptations');
|
|
}
|
|
/**
|
|
* Record HNSW query
|
|
*/
|
|
recordHnswQuery() {
|
|
this.incrementStat('hnsw_queries');
|
|
}
|
|
// ============ Utility Methods ============
|
|
/**
|
|
* Get summary for display (simplified for UI)
|
|
*/
|
|
getSummary() {
|
|
const stats = this.getStats();
|
|
return {
|
|
trajectories: stats.totalTrajectories,
|
|
routings: stats.totalRoutings,
|
|
patterns: stats.totalPatterns,
|
|
operations: stats.sonaAdaptations + stats.hnswQueries,
|
|
};
|
|
}
|
|
/**
|
|
* Get detailed summary for reports
|
|
*/
|
|
getDetailedSummary() {
|
|
const stats = this.getStats();
|
|
const activeCount = this.getActiveTrajectories().length;
|
|
return {
|
|
trajectories: {
|
|
total: stats.totalTrajectories,
|
|
active: activeCount,
|
|
successful: stats.successfulTrajectories,
|
|
},
|
|
routings: {
|
|
total: stats.totalRoutings,
|
|
accuracy: stats.totalRoutings > 0
|
|
? stats.successfulRoutings / stats.totalRoutings
|
|
: 0,
|
|
},
|
|
patterns: stats.totalPatterns,
|
|
operations: {
|
|
sona: stats.sonaAdaptations,
|
|
hnsw: stats.hnswQueries,
|
|
},
|
|
};
|
|
}
|
|
/**
|
|
* Close database connection
|
|
*/
|
|
close() {
|
|
this.db.close();
|
|
IntelligenceStore.instance = null;
|
|
}
|
|
/**
|
|
* Reset all data (for testing)
|
|
*/
|
|
reset() {
|
|
this.db.exec(`
|
|
DELETE FROM trajectories;
|
|
DELETE FROM patterns;
|
|
DELETE FROM routings;
|
|
UPDATE stats SET
|
|
total_trajectories = 0,
|
|
successful_trajectories = 0,
|
|
total_routings = 0,
|
|
successful_routings = 0,
|
|
total_patterns = 0,
|
|
sona_adaptations = 0,
|
|
hnsw_queries = 0,
|
|
last_updated = strftime('%s', 'now')
|
|
WHERE id = 1;
|
|
`);
|
|
}
|
|
}
|
|
// Export singleton getter
|
|
export function getIntelligenceStore(dbPath) {
|
|
return IntelligenceStore.getInstance(dbPath);
|
|
}
|
|
//# sourceMappingURL=IntelligenceStore.js.map
|