mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-26 16:04:02 +00:00
* feat(postgres): Add RuVector Postgres v2 implementation plan Complete specification for RuVector Postgres v2 with: Architecture: - PostgreSQL extension (pgrx) with hybrid architecture - SQL handles ACID/joins, RuVector engine handles vectors/graphs/learning - Backward compatible with pgvector SQL surface - Shared memory IPC with bounded contracts (64KB inline, 16MB shared) 4-Phase Implementation: - Phase 1: pgvector-compatible search (1a: function-based, 1b: Index AM) - Phase 2: Tiered storage with compression and exactness GUC - Phase 3: Graph engine with Cypher and SQL join keys - Phase 4: Dynamic mincut integrity gating (key differentiator) Key Technical Details: - lambda_cut: Minimum cut value via Stoer-Wagner (PRIMARY integrity metric) - lambda2: Algebraic connectivity (OPTIONAL drift signal) - DIFFERENT from mincut! - Contracted operational graph (~1000 nodes) - never compute on full similarity graph - Hysteresis model with consecutive samples and cooldown - Operation risk classification (Low/Medium/High) - MVCC visibility with incremental paging API - WAL replay with idempotency and LSN ordering - Partition map versioning and epoch fencing for cluster mode Files: - 00-overview.md: Architecture, consistency contract, benchmark spec - 01-sql-schema.md: SQL schema and types - 02-background-workers.md: IPC contract, mincut worker - 03-index-access-methods.md: Index AM specification - 04-integrity-events.md: Events, hysteresis, operation classes - 05-phase1-pgvector-compat.md: Phase 1a/1b incremental path - 06-phase2-tiered-storage.md: Tiered storage with GUC exactness - 07-phase3-graph-cypher.md: Graph engine with SQL joins - 08-phase4-integrity-control.md: Mincut gating with Stoer-Wagner - 09-migration-guide.md: Migration from pgvector - 10-consistency-replication.md: Consistency and replication model 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs(postgres): Rewrite v2 overview with compelling framing Replace technical executive summary with clear explanation of why RuVector matters: - From symptom monitoring to causal monitoring - Mincut as leading indicator, not metric - Algorithm becomes control signal (control plane, not analytics) - Failure mode class change: cascading → graceful degradation - Explainable operations via witness edges Key message: "We're not making vector search faster. We're making vector infrastructure survivable." 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(postgres): Add hybrid search, multi-tenancy, and self-healing specs Three high-impact additions to RuVector Postgres v2: ## 11-hybrid-search.md - BM25 + Vector Fusion - Single query combines semantic and keyword search - Proper BM25 implementation (not just ts_rank) - Fusion algorithms: RRF (default), linear, learned - Integrity-aware degradation (stress → single branch) - Parallel branch execution - GUC configuration ## 12-multi-tenancy.md - First-Class Tenant Isolation - SET ruvector.tenant_id for transparent scoping - Isolation levels: shared, partition, dedicated - Automatic promotion based on vector count - Per-tenant integrity (stress in one doesn't affect others) - Per-tenant contracted graphs - Resource quotas and rate limiting - Fair scheduling (no noisy neighbors) - RLS integration for defense in depth ## 13-self-healing.md - Automated Remediation - Completes the control loop: sensor → actuator - Problem classification from witness edges: - Hotspot congestion - Centroid skew - Replication lag - Maintenance contention - Index fragmentation - Memory pressure - Built-in strategies: - Rebalance partitions - Pause maintenance jobs - Throttle ingestion - Scale read replicas (K8s) - Compact fragmented indexes - Safety: reversible actions, blast radius limits - Learning: outcome tracking, strategy weight updates - The key insight: "We built the sensor. Now we build the actuator." 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(intelligence): Add self-learning intelligence layer with v3 features Comprehensive intelligence system for Claude Code hooks: Core Features (v2): - VectorMemory with @ruvector/core native HNSW (150x faster) - Hyperbolic distance (Poincaré ball) for hierarchical embeddings - ReasoningBank with Q-learning and pattern decay (7-day half-life) - Confidence Calibration tracking (predicted vs actual accuracy) - A/B Testing with 10% holdout for measuring intelligence lift - Feedback Loop for tracking suggestion follow-through - Active Learning for identifying uncertain states v3 Improvements: - Error Pattern Learning (Rust E0xxx, TypeScript TSxxxx, npm errors) - File Sequence Learning (tracks which files are edited together) - Test Suggestion Triggers (suggests cargo test after source edits) - Hive-Mind swarm coordination (11 agents, 38 edges) Pretrained from memory.db: - 7,697 commands processed - 4,023 vector memories - 117 Q-table states with decay metadata - 8,520 calibration samples Anti-overfitting measures: - Q-values capped at 0.8, floored at -0.5 - Decaying learning rate: 0.3/sqrt(count) - Pattern decay with timestamps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(intelligence): Fix Q-table lookups - learning now has real effect Three critical bugs were preventing the intelligence layer from using learned patterns: 1. State format mismatch: CLI used spaces ("editing rs in project") but Q-table used underscores ("edit_rs_in_project") - Fixed in cli.js: all states now use underscore format 2. stateKey() hyphen normalization: Function converted hyphens to underscores, but Q-table keys had hyphens (e.g. "ruvector-core") - Fixed regex: /[^a-z0-9-]+/g preserves hyphens 3. A/B testing control group: 10% random sessions ignored learning - Reduced holdout to 5% with persistent session assignment - Added INTELLIGENCE_MODE=treatment env override for development Result: Agent recommendations now show 80% confidence for Rust files using learned Q-values, instead of 0% with random selection. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(hooks): Display intelligence guidance to Claude in foreground Critical fix: PreToolUse hooks were running in background (&) which meant Claude never saw the intelligence output. Now: - PreToolUse: Foreground execution (Claude sees guidance) - pre-edit: Shows recommended agent + confidence + similar edits - pre-command: Shows command patterns + suggestions - Added 3s timeout to prevent blocking - PostToolUse: Background execution (async learning) - post-edit: Records success/failure, learns patterns - post-command: Captures errors, updates Q-values - SessionStart: New hook shows learned patterns at session start - Displays pattern count, memory stats - Shows top 3 learned state-action pairs with Q-values Claude now receives self-learning guidance like: "🧠 Intelligence Analysis: 📁 ruvector-core/lib.rs 🤖 Recommended: rust-developer (80% confidence) 📚 3 similar past edits found" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
240 lines
7.9 KiB
JavaScript
240 lines
7.9 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* RuVector Intelligence Validation Suite
|
||
*
|
||
* Validates pretrained data for:
|
||
* - Q-table integrity (no overfitting)
|
||
* - Vector memory retrieval
|
||
* - Swarm graph connectivity
|
||
* - Agent routing accuracy
|
||
*/
|
||
|
||
import { readFileSync, existsSync } from 'fs';
|
||
import { join, dirname } from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const DATA_DIR = join(__dirname, '..', 'data');
|
||
|
||
const results = { passed: 0, failed: 0, warnings: 0 };
|
||
|
||
function test(name, fn) {
|
||
try {
|
||
const result = fn();
|
||
if (result === true) {
|
||
console.log(` ✅ ${name}`);
|
||
results.passed++;
|
||
} else if (result === 'warn') {
|
||
console.log(` ⚠️ ${name}`);
|
||
results.warnings++;
|
||
} else {
|
||
console.log(` ❌ ${name}: ${result}`);
|
||
results.failed++;
|
||
}
|
||
} catch (e) {
|
||
console.log(` ❌ ${name}: ${e.message}`);
|
||
results.failed++;
|
||
}
|
||
}
|
||
|
||
console.log('\n🧠 RuVector Intelligence Validation');
|
||
console.log('====================================\n');
|
||
|
||
// === 1. Data Files Exist ===
|
||
console.log('📁 Data Files:');
|
||
const requiredFiles = ['patterns.json', 'memory.json', 'trajectories.json', 'coordination-graph.json', 'swarm-state.json'];
|
||
for (const file of requiredFiles) {
|
||
test(`${file} exists`, () => {
|
||
return existsSync(join(DATA_DIR, file)) || `File not found`;
|
||
});
|
||
}
|
||
|
||
// === 2. Q-Table Validation ===
|
||
console.log('\n📊 Q-Table (patterns.json):');
|
||
const patterns = JSON.parse(readFileSync(join(DATA_DIR, 'patterns.json'), 'utf-8'));
|
||
const states = Object.keys(patterns);
|
||
|
||
test(`Has learned states (${states.length})`, () => {
|
||
return states.length >= 10 || `Only ${states.length} states`;
|
||
});
|
||
|
||
test('No overfitting (Q-values < 0.85)', () => {
|
||
const overfit = [];
|
||
for (const [state, actions] of Object.entries(patterns)) {
|
||
for (const [action, value] of Object.entries(actions)) {
|
||
if (action !== '_count' && typeof value === 'number' && value > 0.85) {
|
||
overfit.push(`${state}:${action}=${value.toFixed(3)}`);
|
||
}
|
||
}
|
||
}
|
||
return overfit.length === 0 || `Overfit: ${overfit.slice(0, 3).join(', ')}...`;
|
||
});
|
||
|
||
test('No negative Q-values below -0.6', () => {
|
||
const tooNegative = [];
|
||
for (const [state, actions] of Object.entries(patterns)) {
|
||
for (const [action, value] of Object.entries(actions)) {
|
||
if (action !== '_count' && typeof value === 'number' && value < -0.6) {
|
||
tooNegative.push(`${state}:${action}=${value.toFixed(3)}`);
|
||
}
|
||
}
|
||
}
|
||
return tooNegative.length === 0 || `Too negative: ${tooNegative.slice(0, 3).join(', ')}`;
|
||
});
|
||
|
||
test('Sample counts are tracked', () => {
|
||
const withCounts = states.filter(s => patterns[s]._count > 0);
|
||
return withCounts.length > 0 || 'No _count fields found';
|
||
});
|
||
|
||
// Q-value distribution check
|
||
const qValues = [];
|
||
for (const actions of Object.values(patterns)) {
|
||
for (const [k, v] of Object.entries(actions)) {
|
||
if (k !== '_count' && typeof v === 'number') qValues.push(v);
|
||
}
|
||
}
|
||
const avgQ = qValues.reduce((a, b) => a + b, 0) / qValues.length;
|
||
const minQ = Math.min(...qValues);
|
||
const maxQ = Math.max(...qValues);
|
||
|
||
test(`Q-value range is reasonable (${minQ.toFixed(2)} to ${maxQ.toFixed(2)})`, () => {
|
||
return maxQ <= 0.85 && minQ >= -0.6 || `Range too extreme`;
|
||
});
|
||
|
||
test(`Average Q-value not too high (avg=${avgQ.toFixed(3)})`, () => {
|
||
return avgQ < 0.7 || 'warn';
|
||
});
|
||
|
||
// === 3. Vector Memory Validation ===
|
||
console.log('\n🧠 Vector Memory (memory.json):');
|
||
const memory = JSON.parse(readFileSync(join(DATA_DIR, 'memory.json'), 'utf-8'));
|
||
|
||
test(`Has memories (${memory.length})`, () => {
|
||
return memory.length > 100 || `Only ${memory.length} memories`;
|
||
});
|
||
|
||
test('Memories have embeddings', () => {
|
||
const withEmbeddings = memory.filter(m => m.embedding && m.embedding.length === 128);
|
||
return withEmbeddings.length === memory.length || `${memory.length - withEmbeddings.length} missing embeddings`;
|
||
});
|
||
|
||
test('Embeddings are normalized', () => {
|
||
const sample = memory.slice(0, 10);
|
||
for (const m of sample) {
|
||
if (!m.embedding) continue;
|
||
const magnitude = Math.sqrt(m.embedding.reduce((sum, v) => sum + v * v, 0));
|
||
if (Math.abs(magnitude - 1.0) > 0.01) {
|
||
return `Magnitude ${magnitude.toFixed(3)} not ~1.0`;
|
||
}
|
||
}
|
||
return true;
|
||
});
|
||
|
||
test('Memories have types', () => {
|
||
const types = new Set(memory.map(m => m.type));
|
||
return types.size > 0 || 'No types found';
|
||
});
|
||
|
||
// === 4. Trajectories Validation ===
|
||
console.log('\n📈 Trajectories (trajectories.json):');
|
||
const trajectories = JSON.parse(readFileSync(join(DATA_DIR, 'trajectories.json'), 'utf-8'));
|
||
|
||
test(`Has trajectories (${trajectories.length})`, () => {
|
||
return trajectories.length > 100 || `Only ${trajectories.length} trajectories`;
|
||
});
|
||
|
||
test('Trajectories have required fields', () => {
|
||
const required = ['state', 'action', 'reward'];
|
||
const missing = trajectories.slice(0, 50).filter(t => !required.every(f => t[f] !== undefined));
|
||
return missing.length === 0 || `${missing.length} missing fields`;
|
||
});
|
||
|
||
const rewardDistribution = { positive: 0, negative: 0, neutral: 0 };
|
||
for (const t of trajectories) {
|
||
if (t.reward > 0) rewardDistribution.positive++;
|
||
else if (t.reward < 0) rewardDistribution.negative++;
|
||
else rewardDistribution.neutral++;
|
||
}
|
||
|
||
test(`Reward distribution is realistic`, () => {
|
||
const negativeRatio = rewardDistribution.negative / trajectories.length;
|
||
// Expect some failures but not too many (real systems have ~10-30% failures)
|
||
return negativeRatio < 0.5 || `${(negativeRatio * 100).toFixed(0)}% negative rewards seems high`;
|
||
});
|
||
|
||
// === 5. Swarm Graph Validation ===
|
||
console.log('\n🔗 Swarm Graph (coordination-graph.json):');
|
||
const graph = JSON.parse(readFileSync(join(DATA_DIR, 'coordination-graph.json'), 'utf-8'));
|
||
|
||
test(`Has agent nodes (${Object.keys(graph.nodes || {}).length})`, () => {
|
||
return Object.keys(graph.nodes || {}).length >= 3 || 'Too few agents';
|
||
});
|
||
|
||
test(`Has coordination edges (${Object.keys(graph.edges || {}).length})`, () => {
|
||
return Object.keys(graph.edges || {}).length >= 5 || 'Too few edges';
|
||
});
|
||
|
||
test('Agents have capabilities', () => {
|
||
const withCaps = Object.values(graph.nodes || {}).filter(n => n.capabilities?.length > 0);
|
||
return withCaps.length > 0 || 'No capabilities defined';
|
||
});
|
||
|
||
test('Graph is connected', () => {
|
||
const nodes = Object.keys(graph.nodes || {});
|
||
const edges = Object.keys(graph.edges || {});
|
||
if (nodes.length <= 1) return true;
|
||
|
||
// Simple connectivity check
|
||
const connected = new Set();
|
||
connected.add(nodes[0]);
|
||
|
||
let changed = true;
|
||
while (changed) {
|
||
changed = false;
|
||
for (const edge of edges) {
|
||
const [a, b] = edge.split(':');
|
||
if (connected.has(a) && !connected.has(b)) {
|
||
connected.add(b);
|
||
changed = true;
|
||
}
|
||
if (connected.has(b) && !connected.has(a)) {
|
||
connected.add(a);
|
||
changed = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return connected.size === nodes.length || `Only ${connected.size}/${nodes.length} nodes connected`;
|
||
});
|
||
|
||
// === 6. Swarm State Validation ===
|
||
console.log('\n📋 Swarm State (swarm-state.json):');
|
||
const swarmState = JSON.parse(readFileSync(join(DATA_DIR, 'swarm-state.json'), 'utf-8'));
|
||
|
||
test('Pretrained flag is set', () => {
|
||
return swarmState.pretrained === true || 'Not marked as pretrained';
|
||
});
|
||
|
||
test('Has pretraining timestamp', () => {
|
||
return swarmState.pretrainedAt ? true : 'No timestamp';
|
||
});
|
||
|
||
test('Has stats', () => {
|
||
return swarmState.stats && swarmState.stats.commands > 0 || 'No stats';
|
||
});
|
||
|
||
// === Summary ===
|
||
console.log('\n====================================');
|
||
console.log(`📊 Results: ${results.passed} passed, ${results.failed} failed, ${results.warnings} warnings`);
|
||
|
||
if (results.failed > 0) {
|
||
console.log('\n❌ Validation FAILED - issues found');
|
||
process.exit(1);
|
||
} else if (results.warnings > 0) {
|
||
console.log('\n⚠️ Validation PASSED with warnings');
|
||
process.exit(0);
|
||
} else {
|
||
console.log('\n✅ Validation PASSED - system is healthy');
|
||
process.exit(0);
|
||
}
|