23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
1345 lines
38 KiB
Markdown
1345 lines
38 KiB
Markdown
# ADR-027: RuVector PostgreSQL Integration for Claude-Flow v3
|
|
|
|
**Status:** Proposed
|
|
**Date:** 2026-01-16
|
|
**Author:** System Architecture Designer
|
|
**Version:** 1.0.0
|
|
|
|
## Context
|
|
|
|
Claude-Flow v3 currently uses a hybrid memory backend (ADR-009) combining SQLite for structured queries and AgentDB for vector search. While this approach works well for many use cases, production deployments increasingly require:
|
|
|
|
1. **Scalable Vector Database** - AgentDB (in-memory HNSW) has limitations for datasets exceeding available RAM
|
|
2. **Graph Capabilities** - No native support for graph queries, relationship traversal, or GNN-based analysis
|
|
3. **Advanced Neural Processing** - Limited attention mechanism support for complex semantic understanding
|
|
4. **Hierarchical Data** - Standard Euclidean embeddings poorly represent hierarchical relationships
|
|
5. **Self-Learning Optimization** - No query optimization learning from access patterns
|
|
6. **Production Reliability** - Need for proven database with ACID guarantees, replication, and backup
|
|
|
|
The `@ruvector/postgres-cli` package provides a production-grade PostgreSQL extension with:
|
|
- **53+ SQL functions** for vector and graph operations
|
|
- **39 attention mechanisms** for neural processing
|
|
- **GNN layers** for graph-aware queries
|
|
- **Hyperbolic embeddings** for hierarchical data (Poincare ball model)
|
|
- **Self-learning query optimizer** that improves with usage
|
|
|
|
This creates an opportunity to offer users a high-performance alternative to the current memory backend.
|
|
|
|
## Decision
|
|
|
|
Integrate `@ruvector/postgres-cli` as an **optional plugin bridge** in Claude-Flow v3, following the plugin architecture established in ADR-015-v2. This provides a production-grade vector database option while maintaining backward compatibility with existing AgentDB deployments.
|
|
|
|
### Design Principles
|
|
|
|
1. **Plugin-Based** - Follows ADR-015-v2 unified plugin system
|
|
2. **Optional Dependency** - PostgreSQL not required; graceful fallback to AgentDB
|
|
3. **MCP Tool Exposure** - All vector/graph operations available as MCP tools
|
|
4. **Async-First** - All operations async with batching support
|
|
5. **Security-First** - Credential management, parameterized queries, resource limits
|
|
|
|
## Key Features to Support
|
|
|
|
### 1. Vector Operations (53+ SQL Functions)
|
|
|
|
| Category | Functions | Description |
|
|
|----------|-----------|-------------|
|
|
| **Similarity** | `cosine_similarity`, `euclidean_distance`, `dot_product`, `manhattan_distance` | Distance metrics for vector comparison |
|
|
| **Aggregation** | `vector_avg`, `vector_sum`, `vector_centroid` | Vector aggregation operations |
|
|
| **Transformation** | `vector_normalize`, `vector_quantize`, `vector_project` | Vector transformations |
|
|
| **HNSW Index** | `hnsw_search`, `hnsw_insert`, `hnsw_bulk_insert` | High-performance vector indexing |
|
|
| **Hyperbolic** | `poincare_distance`, `poincare_centroid`, `lorentz_transform` | Hyperbolic geometry operations |
|
|
|
|
### 2. Attention Mechanisms (39 Types)
|
|
|
|
| Mechanism | Use Case |
|
|
|-----------|----------|
|
|
| **Self-Attention** | Intra-sequence relationships |
|
|
| **Multi-Head** | Parallel attention patterns |
|
|
| **Cross-Attention** | Query-document matching |
|
|
| **Sparse Attention** | Long-sequence efficiency |
|
|
| **Linear Attention** | O(n) complexity attention |
|
|
| **Flash Attention** | Memory-efficient GPU attention |
|
|
| **Rotary Position** | Relative position encoding |
|
|
| **ALiBi** | Length extrapolation |
|
|
| **Sliding Window** | Local context attention |
|
|
| **Gated Attention** | Controlled information flow |
|
|
|
|
### 3. Graph Neural Network Layers
|
|
|
|
```sql
|
|
-- Example: GNN-enhanced semantic search
|
|
SELECT * FROM ruvector.gnn_search(
|
|
query_embedding := $1,
|
|
graph_context := 'code_dependencies',
|
|
layers := ARRAY['GAT', 'GraphSAGE'],
|
|
k := 10,
|
|
depth := 2
|
|
);
|
|
```
|
|
|
|
| Layer Type | Description |
|
|
|------------|-------------|
|
|
| **GCN** | Graph Convolutional Network |
|
|
| **GAT** | Graph Attention Network |
|
|
| **GraphSAGE** | Inductive node embedding |
|
|
| **GIN** | Graph Isomorphism Network |
|
|
| **EdgeConv** | Edge-aware convolutions |
|
|
|
|
### 4. Hyperbolic Embeddings
|
|
|
|
Hyperbolic space naturally represents hierarchical relationships (code AST, dependency trees, organizational structures) with exponentially more capacity than Euclidean space.
|
|
|
|
```typescript
|
|
interface HyperbolicConfig {
|
|
model: 'poincare' | 'lorentz' | 'klein';
|
|
curvature: number; // Default: -1.0
|
|
dimensions: number; // Typically 64-256 (less than Euclidean)
|
|
trainable: boolean; // Learn curvature from data
|
|
}
|
|
```
|
|
|
|
### 5. Self-Learning Query Optimization
|
|
|
|
The query optimizer learns from access patterns to:
|
|
- **Index Selection** - Automatically choose optimal indexes
|
|
- **Query Rewriting** - Optimize query plans based on data distribution
|
|
- **Cache Warming** - Pre-load frequently accessed vectors
|
|
- **Partition Routing** - Direct queries to relevant partitions
|
|
|
|
```sql
|
|
-- Enable self-learning optimizer
|
|
SELECT ruvector.enable_learning_optimizer(
|
|
learning_rate := 0.01,
|
|
exploration_factor := 0.1,
|
|
min_samples := 1000
|
|
);
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Plugin Structure
|
|
|
|
```
|
|
v3/@claude-flow/plugins/src/
|
|
├── bridges/
|
|
│ └── ruvector-postgres/
|
|
│ ├── index.ts # Plugin entry point
|
|
│ ├── plugin.ts # IPlugin implementation
|
|
│ ├── connection-manager.ts # PostgreSQL connection pooling
|
|
│ ├── query-builder.ts # SQL query builder
|
|
│ ├── embedding-adapter.ts # Embedding format conversion
|
|
│ ├── graph-adapter.ts # Graph operations adapter
|
|
│ ├── attention-adapter.ts # Attention mechanism adapter
|
|
│ ├── migration-helper.ts # AgentDB migration utilities
|
|
│ └── types.ts # TypeScript interfaces
|
|
├── mcp-tools/
|
|
│ └── ruvector-postgres-tools.ts # MCP tool definitions
|
|
└── collections/
|
|
└── storage/
|
|
└── ruvector-postgres.ts # Collection entry
|
|
```
|
|
|
|
### Plugin Implementation
|
|
|
|
```typescript
|
|
// v3/@claude-flow/plugins/src/bridges/ruvector-postgres/plugin.ts
|
|
|
|
import { IPlugin, PluginMetadata, PluginContext } from '../../core/plugin-interface.js';
|
|
import { ConnectionManager } from './connection-manager.js';
|
|
import { QueryBuilder } from './query-builder.js';
|
|
|
|
export class RuVectorPostgresPlugin implements IPlugin {
|
|
readonly metadata: PluginMetadata = {
|
|
name: 'ruvector-postgres',
|
|
version: '1.0.0',
|
|
description: 'RuVector PostgreSQL integration for high-performance vector/graph operations',
|
|
author: 'Claude Flow Team',
|
|
tags: ['vector', 'graph', 'postgresql', 'storage', 'production'],
|
|
dependencies: [
|
|
{ name: 'core-plugin', version: '^3.0.0' }
|
|
],
|
|
capabilities: ['network', 'memory'],
|
|
};
|
|
|
|
private connectionManager: ConnectionManager | null = null;
|
|
private queryBuilder: QueryBuilder | null = null;
|
|
private context: PluginContext | null = null;
|
|
|
|
async initialize(context: PluginContext): Promise<void> {
|
|
this.context = context;
|
|
|
|
const config = context.config.get<RuVectorPostgresConfig>('ruvector-postgres');
|
|
if (!config) {
|
|
context.logger.warn('RuVector PostgreSQL not configured, plugin disabled');
|
|
return;
|
|
}
|
|
|
|
// Initialize connection pool
|
|
this.connectionManager = new ConnectionManager({
|
|
host: config.host,
|
|
port: config.port,
|
|
database: config.database,
|
|
user: config.user,
|
|
password: config.password,
|
|
ssl: config.ssl,
|
|
poolSize: config.poolSize ?? 10,
|
|
idleTimeout: config.idleTimeout ?? 30000,
|
|
connectionTimeout: config.connectionTimeout ?? 5000,
|
|
});
|
|
|
|
await this.connectionManager.initialize();
|
|
|
|
// Initialize query builder
|
|
this.queryBuilder = new QueryBuilder({
|
|
schema: config.schema ?? 'ruvector',
|
|
defaultDimensions: config.dimensions ?? 1536,
|
|
enableLearning: config.enableLearning ?? true,
|
|
});
|
|
|
|
// Verify RuVector extension is installed
|
|
await this.verifyExtension();
|
|
|
|
context.logger.info('RuVector PostgreSQL plugin initialized');
|
|
}
|
|
|
|
async shutdown(): Promise<void> {
|
|
if (this.connectionManager) {
|
|
await this.connectionManager.shutdown();
|
|
this.connectionManager = null;
|
|
}
|
|
this.context?.logger.info('RuVector PostgreSQL plugin shut down');
|
|
}
|
|
|
|
getMCPTools(): MCPTool[] {
|
|
return [
|
|
this.createVectorSearchTool(),
|
|
this.createGraphSearchTool(),
|
|
this.createAttentionQueryTool(),
|
|
this.createBulkInsertTool(),
|
|
this.createHyperbolicSearchTool(),
|
|
this.createOptimizeIndexTool(),
|
|
];
|
|
}
|
|
|
|
private async verifyExtension(): Promise<void> {
|
|
const result = await this.connectionManager!.query(
|
|
"SELECT extversion FROM pg_extension WHERE extname = 'ruvector'"
|
|
);
|
|
if (result.rows.length === 0) {
|
|
throw new Error(
|
|
'RuVector PostgreSQL extension not installed. ' +
|
|
'Install with: CREATE EXTENSION ruvector;'
|
|
);
|
|
}
|
|
}
|
|
|
|
// Tool implementations...
|
|
}
|
|
```
|
|
|
|
### Connection Pooling
|
|
|
|
```typescript
|
|
// v3/@claude-flow/plugins/src/bridges/ruvector-postgres/connection-manager.ts
|
|
|
|
import { Pool, PoolClient, PoolConfig } from 'pg';
|
|
|
|
export interface ConnectionManagerConfig {
|
|
host: string;
|
|
port: number;
|
|
database: string;
|
|
user: string;
|
|
password: string;
|
|
ssl?: boolean | object;
|
|
poolSize: number;
|
|
idleTimeout: number;
|
|
connectionTimeout: number;
|
|
}
|
|
|
|
export class ConnectionManager {
|
|
private pool: Pool | null = null;
|
|
private config: ConnectionManagerConfig;
|
|
private healthCheckInterval: NodeJS.Timer | null = null;
|
|
private stats = {
|
|
totalConnections: 0,
|
|
activeConnections: 0,
|
|
idleConnections: 0,
|
|
waitingClients: 0,
|
|
totalQueries: 0,
|
|
failedQueries: 0,
|
|
avgQueryTime: 0,
|
|
};
|
|
|
|
constructor(config: ConnectionManagerConfig) {
|
|
this.config = config;
|
|
}
|
|
|
|
async initialize(): Promise<void> {
|
|
const poolConfig: PoolConfig = {
|
|
host: this.config.host,
|
|
port: this.config.port,
|
|
database: this.config.database,
|
|
user: this.config.user,
|
|
password: this.config.password,
|
|
ssl: this.config.ssl,
|
|
max: this.config.poolSize,
|
|
idleTimeoutMillis: this.config.idleTimeout,
|
|
connectionTimeoutMillis: this.config.connectionTimeout,
|
|
};
|
|
|
|
this.pool = new Pool(poolConfig);
|
|
|
|
// Set up event listeners
|
|
this.pool.on('connect', () => {
|
|
this.stats.totalConnections++;
|
|
this.stats.activeConnections++;
|
|
});
|
|
|
|
this.pool.on('remove', () => {
|
|
this.stats.activeConnections--;
|
|
});
|
|
|
|
this.pool.on('error', (err) => {
|
|
this.stats.failedQueries++;
|
|
console.error('PostgreSQL pool error:', err);
|
|
});
|
|
|
|
// Verify connection
|
|
const client = await this.pool.connect();
|
|
try {
|
|
await client.query('SELECT 1');
|
|
} finally {
|
|
client.release();
|
|
}
|
|
|
|
// Start health check
|
|
this.healthCheckInterval = setInterval(
|
|
() => this.performHealthCheck(),
|
|
30000
|
|
);
|
|
}
|
|
|
|
async shutdown(): Promise<void> {
|
|
if (this.healthCheckInterval) {
|
|
clearInterval(this.healthCheckInterval);
|
|
}
|
|
if (this.pool) {
|
|
await this.pool.end();
|
|
this.pool = null;
|
|
}
|
|
}
|
|
|
|
async query<T = unknown>(
|
|
sql: string,
|
|
params?: unknown[]
|
|
): Promise<{ rows: T[]; rowCount: number }> {
|
|
if (!this.pool) {
|
|
throw new Error('Connection pool not initialized');
|
|
}
|
|
|
|
const start = Date.now();
|
|
this.stats.totalQueries++;
|
|
|
|
try {
|
|
const result = await this.pool.query(sql, params);
|
|
const duration = Date.now() - start;
|
|
this.updateAvgQueryTime(duration);
|
|
return { rows: result.rows, rowCount: result.rowCount ?? 0 };
|
|
} catch (error) {
|
|
this.stats.failedQueries++;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async withTransaction<T>(
|
|
fn: (client: PoolClient) => Promise<T>
|
|
): Promise<T> {
|
|
if (!this.pool) {
|
|
throw new Error('Connection pool not initialized');
|
|
}
|
|
|
|
const client = await this.pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
const result = await fn(client);
|
|
await client.query('COMMIT');
|
|
return result;
|
|
} catch (error) {
|
|
await client.query('ROLLBACK');
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async batch<T>(
|
|
operations: Array<{ sql: string; params?: unknown[] }>
|
|
): Promise<T[]> {
|
|
return this.withTransaction(async (client) => {
|
|
const results: T[] = [];
|
|
for (const op of operations) {
|
|
const result = await client.query(op.sql, op.params);
|
|
results.push(result.rows as T);
|
|
}
|
|
return results;
|
|
});
|
|
}
|
|
|
|
getStats(): typeof this.stats {
|
|
return { ...this.stats };
|
|
}
|
|
|
|
private async performHealthCheck(): Promise<void> {
|
|
try {
|
|
await this.query('SELECT 1');
|
|
} catch (error) {
|
|
console.error('Health check failed:', error);
|
|
}
|
|
}
|
|
|
|
private updateAvgQueryTime(duration: number): void {
|
|
const n = this.stats.totalQueries;
|
|
this.stats.avgQueryTime =
|
|
(this.stats.avgQueryTime * (n - 1) + duration) / n;
|
|
}
|
|
}
|
|
```
|
|
|
|
### MCP Tool Definitions
|
|
|
|
```typescript
|
|
// v3/@claude-flow/plugins/src/mcp-tools/ruvector-postgres-tools.ts
|
|
|
|
import type { MCPTool } from '../core/types.js';
|
|
|
|
export const ruvectorPostgresTools: MCPTool[] = [
|
|
{
|
|
name: 'ruvector-postgres/vector-search',
|
|
description: 'Perform high-performance vector similarity search using PostgreSQL HNSW index',
|
|
category: 'storage',
|
|
version: '1.0.0',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
query: {
|
|
type: 'string',
|
|
description: 'Text query to embed and search'
|
|
},
|
|
embedding: {
|
|
type: 'array',
|
|
items: { type: 'number' },
|
|
description: 'Pre-computed embedding vector (alternative to query)'
|
|
},
|
|
table: {
|
|
type: 'string',
|
|
description: 'Table name to search',
|
|
default: 'embeddings'
|
|
},
|
|
k: {
|
|
type: 'number',
|
|
description: 'Number of results to return',
|
|
default: 10
|
|
},
|
|
metric: {
|
|
type: 'string',
|
|
enum: ['cosine', 'euclidean', 'dot_product', 'manhattan'],
|
|
default: 'cosine'
|
|
},
|
|
threshold: {
|
|
type: 'number',
|
|
description: 'Minimum similarity threshold (0-1)',
|
|
default: 0.7
|
|
},
|
|
filters: {
|
|
type: 'object',
|
|
description: 'Additional SQL WHERE conditions'
|
|
}
|
|
},
|
|
oneOf: [
|
|
{ required: ['query'] },
|
|
{ required: ['embedding'] }
|
|
]
|
|
},
|
|
handler: async (input, context) => {
|
|
const plugin = context.services.get<RuVectorPostgresPlugin>('ruvector-postgres');
|
|
return plugin.vectorSearch(input);
|
|
}
|
|
},
|
|
|
|
{
|
|
name: 'ruvector-postgres/graph-search',
|
|
description: 'Execute graph-aware semantic search using GNN layers',
|
|
category: 'storage',
|
|
version: '1.0.0',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
query: { type: 'string', description: 'Text query' },
|
|
graphContext: {
|
|
type: 'string',
|
|
description: 'Graph context name (e.g., "code_dependencies", "knowledge_graph")'
|
|
},
|
|
layers: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string',
|
|
enum: ['GCN', 'GAT', 'GraphSAGE', 'GIN', 'EdgeConv']
|
|
},
|
|
default: ['GAT']
|
|
},
|
|
depth: {
|
|
type: 'number',
|
|
description: 'Graph traversal depth',
|
|
default: 2
|
|
},
|
|
k: { type: 'number', default: 10 }
|
|
},
|
|
required: ['query', 'graphContext']
|
|
},
|
|
handler: async (input, context) => {
|
|
const plugin = context.services.get<RuVectorPostgresPlugin>('ruvector-postgres');
|
|
return plugin.graphSearch(input);
|
|
}
|
|
},
|
|
|
|
{
|
|
name: 'ruvector-postgres/attention-query',
|
|
description: 'Execute attention-weighted semantic query with configurable mechanism',
|
|
category: 'storage',
|
|
version: '1.0.0',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
query: { type: 'string' },
|
|
documents: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'Documents to attend over (or table name)'
|
|
},
|
|
mechanism: {
|
|
type: 'string',
|
|
enum: [
|
|
'self', 'multi_head', 'cross', 'sparse', 'linear',
|
|
'flash', 'rotary', 'alibi', 'sliding_window', 'gated'
|
|
],
|
|
default: 'multi_head'
|
|
},
|
|
heads: { type: 'number', default: 8 },
|
|
contextWindow: { type: 'number', default: 4096 }
|
|
},
|
|
required: ['query']
|
|
},
|
|
handler: async (input, context) => {
|
|
const plugin = context.services.get<RuVectorPostgresPlugin>('ruvector-postgres');
|
|
return plugin.attentionQuery(input);
|
|
}
|
|
},
|
|
|
|
{
|
|
name: 'ruvector-postgres/bulk-insert',
|
|
description: 'Bulk insert vectors with automatic batching (52,000+ inserts/sec)',
|
|
category: 'storage',
|
|
version: '1.0.0',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
table: { type: 'string', default: 'embeddings' },
|
|
entries: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
content: { type: 'string' },
|
|
embedding: { type: 'array', items: { type: 'number' } },
|
|
metadata: { type: 'object' }
|
|
},
|
|
required: ['id', 'content']
|
|
}
|
|
},
|
|
batchSize: { type: 'number', default: 1000 },
|
|
generateEmbeddings: { type: 'boolean', default: true }
|
|
},
|
|
required: ['entries']
|
|
},
|
|
handler: async (input, context) => {
|
|
const plugin = context.services.get<RuVectorPostgresPlugin>('ruvector-postgres');
|
|
return plugin.bulkInsert(input);
|
|
}
|
|
},
|
|
|
|
{
|
|
name: 'ruvector-postgres/hyperbolic-search',
|
|
description: 'Search using hyperbolic embeddings for hierarchical data',
|
|
category: 'storage',
|
|
version: '1.0.0',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
query: { type: 'string' },
|
|
model: {
|
|
type: 'string',
|
|
enum: ['poincare', 'lorentz', 'klein'],
|
|
default: 'poincare'
|
|
},
|
|
curvature: { type: 'number', default: -1.0 },
|
|
k: { type: 'number', default: 10 },
|
|
includeAncestors: { type: 'boolean', default: false },
|
|
includeDescendants: { type: 'boolean', default: false }
|
|
},
|
|
required: ['query']
|
|
},
|
|
handler: async (input, context) => {
|
|
const plugin = context.services.get<RuVectorPostgresPlugin>('ruvector-postgres');
|
|
return plugin.hyperbolicSearch(input);
|
|
}
|
|
},
|
|
|
|
{
|
|
name: 'ruvector-postgres/optimize',
|
|
description: 'Optimize indexes and enable self-learning query optimizer',
|
|
category: 'storage',
|
|
version: '1.0.0',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
table: { type: 'string' },
|
|
operations: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string',
|
|
enum: [
|
|
'rebuild_hnsw', 'analyze', 'vacuum',
|
|
'enable_learning', 'warmup_cache', 'create_partitions'
|
|
]
|
|
},
|
|
default: ['analyze']
|
|
},
|
|
learningConfig: {
|
|
type: 'object',
|
|
properties: {
|
|
learningRate: { type: 'number', default: 0.01 },
|
|
explorationFactor: { type: 'number', default: 0.1 },
|
|
minSamples: { type: 'number', default: 1000 }
|
|
}
|
|
}
|
|
},
|
|
required: ['table']
|
|
},
|
|
handler: async (input, context) => {
|
|
const plugin = context.services.get<RuVectorPostgresPlugin>('ruvector-postgres');
|
|
return plugin.optimize(input);
|
|
}
|
|
}
|
|
];
|
|
```
|
|
|
|
### Async Operations with Batching
|
|
|
|
```typescript
|
|
// v3/@claude-flow/plugins/src/bridges/ruvector-postgres/embedding-adapter.ts
|
|
|
|
export class EmbeddingAdapter {
|
|
private connectionManager: ConnectionManager;
|
|
private embeddingGenerator: (text: string) => Promise<Float32Array>;
|
|
private batchQueue: BatchItem[] = [];
|
|
private batchTimeout: NodeJS.Timer | null = null;
|
|
private batchSize = 1000;
|
|
private flushInterval = 100; // ms
|
|
|
|
constructor(
|
|
connectionManager: ConnectionManager,
|
|
embeddingGenerator: (text: string) => Promise<Float32Array>,
|
|
config?: { batchSize?: number; flushInterval?: number }
|
|
) {
|
|
this.connectionManager = connectionManager;
|
|
this.embeddingGenerator = embeddingGenerator;
|
|
this.batchSize = config?.batchSize ?? 1000;
|
|
this.flushInterval = config?.flushInterval ?? 100;
|
|
}
|
|
|
|
async insert(entry: EmbeddingEntry): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
this.batchQueue.push({ entry, resolve, reject });
|
|
this.scheduleBatchFlush();
|
|
});
|
|
}
|
|
|
|
async bulkInsert(
|
|
entries: EmbeddingEntry[],
|
|
options?: { generateEmbeddings?: boolean }
|
|
): Promise<{ inserted: number; duration: number }> {
|
|
const start = Date.now();
|
|
|
|
// Generate embeddings in parallel batches if needed
|
|
if (options?.generateEmbeddings !== false) {
|
|
const embeddingBatches = this.chunk(entries, 100);
|
|
for (const batch of embeddingBatches) {
|
|
await Promise.all(
|
|
batch
|
|
.filter(e => !e.embedding)
|
|
.map(async (entry) => {
|
|
entry.embedding = await this.embeddingGenerator(entry.content);
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
// Insert in batches
|
|
const insertBatches = this.chunk(entries, this.batchSize);
|
|
let inserted = 0;
|
|
|
|
for (const batch of insertBatches) {
|
|
const values = batch.map((e, i) => {
|
|
const offset = i * 4;
|
|
return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4})`;
|
|
}).join(', ');
|
|
|
|
const params = batch.flatMap(e => [
|
|
e.id,
|
|
e.content,
|
|
`[${Array.from(e.embedding!).join(',')}]`,
|
|
JSON.stringify(e.metadata ?? {})
|
|
]);
|
|
|
|
const sql = `
|
|
INSERT INTO embeddings (id, content, embedding, metadata)
|
|
VALUES ${values}
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
content = EXCLUDED.content,
|
|
embedding = EXCLUDED.embedding,
|
|
metadata = EXCLUDED.metadata,
|
|
updated_at = NOW()
|
|
`;
|
|
|
|
const result = await this.connectionManager.query(sql, params);
|
|
inserted += result.rowCount;
|
|
}
|
|
|
|
return {
|
|
inserted,
|
|
duration: Date.now() - start
|
|
};
|
|
}
|
|
|
|
private scheduleBatchFlush(): void {
|
|
if (this.batchQueue.length >= this.batchSize) {
|
|
this.flushBatch();
|
|
return;
|
|
}
|
|
|
|
if (!this.batchTimeout) {
|
|
this.batchTimeout = setTimeout(() => {
|
|
this.batchTimeout = null;
|
|
if (this.batchQueue.length > 0) {
|
|
this.flushBatch();
|
|
}
|
|
}, this.flushInterval);
|
|
}
|
|
}
|
|
|
|
private async flushBatch(): Promise<void> {
|
|
const batch = this.batchQueue.splice(0, this.batchSize);
|
|
if (batch.length === 0) return;
|
|
|
|
try {
|
|
const entries = batch.map(b => b.entry);
|
|
await this.bulkInsert(entries);
|
|
batch.forEach(b => b.resolve());
|
|
} catch (error) {
|
|
batch.forEach(b => b.reject(error));
|
|
}
|
|
}
|
|
|
|
private chunk<T>(array: T[], size: number): T[][] {
|
|
const chunks: T[][] = [];
|
|
for (let i = 0; i < array.length; i += size) {
|
|
chunks.push(array.slice(i, i + size));
|
|
}
|
|
return chunks;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Performance Targets
|
|
|
|
| Metric | Target | Comparison to AgentDB |
|
|
|--------|--------|----------------------|
|
|
| **Bulk Insert Rate** | 52,000+ inserts/second | 10x faster (batched) |
|
|
| **Vector Search Latency** | <1ms (p99) | Comparable (HNSW) |
|
|
| **Search Speedup** | 150x-12,500x vs linear | Same (HNSW algorithm) |
|
|
| **Graph Query Latency** | <10ms (2-hop) | N/A (new capability) |
|
|
| **Attention Query** | <50ms (4K context) | N/A (new capability) |
|
|
| **Memory Efficiency** | Disk-based + caching | Better for large datasets |
|
|
| **Concurrent Queries** | 100+ parallel | Better (connection pool) |
|
|
| **Dataset Size** | TB-scale | GB-scale (memory bound) |
|
|
|
|
### Benchmark Configuration
|
|
|
|
```typescript
|
|
// Expected benchmark results
|
|
const benchmarkTargets = {
|
|
bulkInsert: {
|
|
targetOpsPerSec: 52000,
|
|
batchSize: 1000,
|
|
vectorDimensions: 1536
|
|
},
|
|
vectorSearch: {
|
|
targetLatencyP50: 0.5, // ms
|
|
targetLatencyP99: 1.0, // ms
|
|
datasetSize: 1_000_000,
|
|
k: 10
|
|
},
|
|
graphSearch: {
|
|
targetLatencyP50: 5, // ms
|
|
targetLatencyP99: 10, // ms
|
|
graphNodes: 100_000,
|
|
depth: 2
|
|
},
|
|
attentionQuery: {
|
|
targetLatencyP50: 20, // ms
|
|
targetLatencyP99: 50, // ms
|
|
contextLength: 4096,
|
|
heads: 8
|
|
}
|
|
};
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
### 1. Connection Credential Management
|
|
|
|
```typescript
|
|
// Configuration with secure credential handling
|
|
interface RuVectorPostgresConfig {
|
|
// Direct credentials (development only)
|
|
host?: string;
|
|
port?: number;
|
|
database?: string;
|
|
user?: string;
|
|
password?: string;
|
|
|
|
// Secure credential sources (production)
|
|
connectionString?: string; // From environment variable
|
|
credentialProvider?: CredentialProvider; // AWS Secrets Manager, Vault, etc.
|
|
sslCertPath?: string; // Client certificate auth
|
|
|
|
// SSL/TLS configuration
|
|
ssl?: {
|
|
rejectUnauthorized: boolean;
|
|
ca?: string;
|
|
cert?: string;
|
|
key?: string;
|
|
};
|
|
}
|
|
|
|
// Example secure configuration
|
|
const secureConfig: RuVectorPostgresConfig = {
|
|
connectionString: process.env.RUVECTOR_DATABASE_URL,
|
|
ssl: {
|
|
rejectUnauthorized: true,
|
|
ca: fs.readFileSync('/etc/ssl/certs/rds-ca-2019-root.pem').toString()
|
|
}
|
|
};
|
|
```
|
|
|
|
### 2. Query Parameterization
|
|
|
|
All queries use parameterized statements to prevent SQL injection:
|
|
|
|
```typescript
|
|
// NEVER do this:
|
|
// const sql = `SELECT * FROM embeddings WHERE id = '${userInput}'`;
|
|
|
|
// ALWAYS use parameterized queries:
|
|
const sql = 'SELECT * FROM embeddings WHERE id = $1';
|
|
const result = await connectionManager.query(sql, [userInput]);
|
|
|
|
// Vector queries with proper escaping
|
|
const vectorSql = `
|
|
SELECT id, content, embedding <=> $1::vector AS distance
|
|
FROM embeddings
|
|
WHERE embedding <=> $1::vector < $2
|
|
ORDER BY distance
|
|
LIMIT $3
|
|
`;
|
|
const result = await connectionManager.query(vectorSql, [
|
|
`[${embedding.join(',')}]`,
|
|
threshold,
|
|
k
|
|
]);
|
|
```
|
|
|
|
### 3. Resource Limits
|
|
|
|
```typescript
|
|
interface ResourceLimits {
|
|
// Connection limits
|
|
maxPoolSize: number; // Default: 10
|
|
maxIdleConnections: number; // Default: 5
|
|
connectionTimeout: number; // Default: 5000ms
|
|
|
|
// Query limits
|
|
maxQueryTimeout: number; // Default: 30000ms
|
|
maxResultRows: number; // Default: 10000
|
|
maxBatchSize: number; // Default: 5000
|
|
|
|
// Memory limits
|
|
maxVectorDimensions: number; // Default: 4096
|
|
maxConcurrentEmbeddings: number; // Default: 100
|
|
|
|
// Rate limits
|
|
maxQueriesPerMinute: number; // Default: 1000
|
|
maxInsertsPerMinute: number; // Default: 100000
|
|
}
|
|
|
|
// Enforcement in query execution
|
|
async query(sql: string, params?: unknown[]): Promise<QueryResult> {
|
|
// Check rate limit
|
|
if (!this.rateLimiter.tryAcquire('query')) {
|
|
throw new RateLimitExceededError('Query rate limit exceeded');
|
|
}
|
|
|
|
// Set query timeout
|
|
const timeoutSql = `SET statement_timeout = ${this.limits.maxQueryTimeout}`;
|
|
await this.pool.query(timeoutSql);
|
|
|
|
// Execute with result limit
|
|
const limitedSql = sql.includes('LIMIT') ? sql : `${sql} LIMIT ${this.limits.maxResultRows}`;
|
|
return this.pool.query(limitedSql, params);
|
|
}
|
|
```
|
|
|
|
### 4. Audit Logging
|
|
|
|
```typescript
|
|
interface AuditLog {
|
|
timestamp: Date;
|
|
operation: 'query' | 'insert' | 'update' | 'delete' | 'admin';
|
|
userId?: string;
|
|
query: string;
|
|
parameters?: unknown[];
|
|
duration: number;
|
|
rowsAffected: number;
|
|
success: boolean;
|
|
errorMessage?: string;
|
|
}
|
|
|
|
// Audit middleware
|
|
async function withAudit<T>(
|
|
operation: string,
|
|
fn: () => Promise<T>
|
|
): Promise<T> {
|
|
const start = Date.now();
|
|
try {
|
|
const result = await fn();
|
|
await logAudit({ operation, success: true, duration: Date.now() - start });
|
|
return result;
|
|
} catch (error) {
|
|
await logAudit({ operation, success: false, error, duration: Date.now() - start });
|
|
throw error;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Migration Path
|
|
|
|
### From AgentDB (ADR-009)
|
|
|
|
The migration provides a backward-compatible layer that allows gradual transition:
|
|
|
|
```typescript
|
|
// v3/@claude-flow/plugins/src/bridges/ruvector-postgres/migration-helper.ts
|
|
|
|
export class MigrationHelper {
|
|
private agentDB: AgentDBAdapter;
|
|
private postgres: RuVectorPostgresPlugin;
|
|
|
|
constructor(agentDB: AgentDBAdapter, postgres: RuVectorPostgresPlugin) {
|
|
this.agentDB = agentDB;
|
|
this.postgres = postgres;
|
|
}
|
|
|
|
/**
|
|
* Export all data from AgentDB to PostgreSQL
|
|
*/
|
|
async exportToPostgres(options?: {
|
|
batchSize?: number;
|
|
onProgress?: (progress: MigrationProgress) => void;
|
|
}): Promise<MigrationResult> {
|
|
const batchSize = options?.batchSize ?? 1000;
|
|
const stats = { total: 0, migrated: 0, failed: 0, duration: 0 };
|
|
const start = Date.now();
|
|
|
|
// Get all namespaces from AgentDB
|
|
const namespaces = await this.agentDB.listNamespaces();
|
|
|
|
for (const namespace of namespaces) {
|
|
const entries = await this.agentDB.getAll(namespace);
|
|
stats.total += entries.length;
|
|
|
|
// Batch insert into PostgreSQL
|
|
for (let i = 0; i < entries.length; i += batchSize) {
|
|
const batch = entries.slice(i, i + batchSize);
|
|
try {
|
|
await this.postgres.bulkInsert({
|
|
table: `embeddings_${namespace}`,
|
|
entries: batch.map(e => ({
|
|
id: e.id,
|
|
content: e.content,
|
|
embedding: e.embedding,
|
|
metadata: { ...e.metadata, namespace }
|
|
})),
|
|
generateEmbeddings: false // Already have embeddings
|
|
});
|
|
stats.migrated += batch.length;
|
|
} catch (error) {
|
|
stats.failed += batch.length;
|
|
console.error(`Migration batch failed:`, error);
|
|
}
|
|
|
|
options?.onProgress?.({
|
|
...stats,
|
|
percentage: (stats.migrated + stats.failed) / stats.total * 100
|
|
});
|
|
}
|
|
}
|
|
|
|
stats.duration = Date.now() - start;
|
|
return stats;
|
|
}
|
|
|
|
/**
|
|
* Create a dual-write adapter that writes to both backends
|
|
*/
|
|
createDualWriteAdapter(): IMemoryBackend {
|
|
return new DualWriteAdapter(this.agentDB, this.postgres);
|
|
}
|
|
|
|
/**
|
|
* Create a read-through adapter that reads from PostgreSQL with AgentDB fallback
|
|
*/
|
|
createReadThroughAdapter(): IMemoryBackend {
|
|
return new ReadThroughAdapter(this.postgres, this.agentDB);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dual-write adapter for gradual migration
|
|
*/
|
|
class DualWriteAdapter implements IMemoryBackend {
|
|
constructor(
|
|
private primary: IMemoryBackend,
|
|
private secondary: IMemoryBackend
|
|
) {}
|
|
|
|
async store(entry: MemoryEntry): Promise<void> {
|
|
// Write to both, primary is source of truth
|
|
await Promise.all([
|
|
this.primary.store(entry),
|
|
this.secondary.store(entry).catch(err => {
|
|
console.warn('Secondary write failed:', err);
|
|
})
|
|
]);
|
|
}
|
|
|
|
async get(id: string): Promise<MemoryEntry | null> {
|
|
// Read from primary
|
|
return this.primary.get(id);
|
|
}
|
|
|
|
async search(query: SearchQuery): Promise<SearchResult[]> {
|
|
// Route based on query type
|
|
if (query.type === 'semantic' && this.secondary instanceof RuVectorPostgresPlugin) {
|
|
return this.secondary.search(query);
|
|
}
|
|
return this.primary.search(query);
|
|
}
|
|
|
|
// ... other IMemoryBackend methods
|
|
}
|
|
```
|
|
|
|
### Migration Steps
|
|
|
|
1. **Phase 1: Install and Configure**
|
|
```bash
|
|
# Install PostgreSQL with RuVector extension
|
|
npm install @ruvector/postgres-cli pg
|
|
|
|
# Initialize database
|
|
npx ruvector init --connection-string "$DATABASE_URL"
|
|
```
|
|
|
|
2. **Phase 2: Enable Dual-Write**
|
|
```typescript
|
|
// claude-flow.config.ts
|
|
export default {
|
|
memory: {
|
|
backend: 'dual-write',
|
|
primary: 'agentdb',
|
|
secondary: {
|
|
type: 'ruvector-postgres',
|
|
connectionString: process.env.RUVECTOR_DATABASE_URL
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
3. **Phase 3: Migrate Existing Data**
|
|
```bash
|
|
npx claude-flow migrate \
|
|
--from agentdb \
|
|
--to ruvector-postgres \
|
|
--batch-size 5000
|
|
```
|
|
|
|
4. **Phase 4: Switch Primary**
|
|
```typescript
|
|
export default {
|
|
memory: {
|
|
backend: 'ruvector-postgres',
|
|
fallback: 'agentdb' // Keep AgentDB as fallback
|
|
}
|
|
};
|
|
```
|
|
|
|
5. **Phase 5: Deprecate AgentDB**
|
|
```typescript
|
|
export default {
|
|
memory: {
|
|
backend: 'ruvector-postgres'
|
|
// AgentDB removed
|
|
}
|
|
};
|
|
```
|
|
|
|
### Backward Compatibility Layer
|
|
|
|
```typescript
|
|
// Ensure existing code continues to work
|
|
const memory = await createMemoryService({
|
|
backend: 'ruvector-postgres',
|
|
// ... config
|
|
});
|
|
|
|
// All existing IMemoryBackend methods work unchanged
|
|
await memory.store(entry);
|
|
const result = await memory.get(id);
|
|
const results = await memory.search({ content: 'query', k: 10 });
|
|
|
|
// New capabilities available via plugin API
|
|
const plugin = memory.getPlugin('ruvector-postgres');
|
|
await plugin.graphSearch({ query: 'code dependencies', graphContext: 'ast' });
|
|
await plugin.attentionQuery({ query: 'complex reasoning', mechanism: 'multi_head' });
|
|
```
|
|
|
|
## Consequences
|
|
|
|
### Positive
|
|
|
|
1. **Production-Grade Storage** - PostgreSQL provides ACID guarantees, replication, backup, and proven reliability at scale
|
|
2. **Graph Capabilities** - Native graph queries enable relationship-aware semantic search (code dependencies, knowledge graphs)
|
|
3. **Advanced Neural Processing** - 39 attention mechanisms enable sophisticated query understanding
|
|
4. **Hierarchical Data Support** - Hyperbolic embeddings naturally represent tree/hierarchy structures
|
|
5. **Self-Learning Optimization** - Query optimizer improves performance over time based on access patterns
|
|
6. **Scalability** - Disk-based storage supports TB-scale datasets beyond RAM limits
|
|
7. **Ecosystem Integration** - PostgreSQL tooling, monitoring, and expertise widely available
|
|
8. **Concurrent Access** - Connection pooling supports high-concurrency workloads
|
|
|
|
### Negative
|
|
|
|
1. **PostgreSQL Dependency** - Requires PostgreSQL 14+ with RuVector extension installed
|
|
2. **Infrastructure Complexity** - Additional database server to manage (unless using managed PostgreSQL)
|
|
3. **Network Latency** - Remote database adds network round-trip vs in-process AgentDB
|
|
4. **Learning Curve** - New SQL functions and concepts to learn
|
|
5. **Cost** - Managed PostgreSQL services incur additional cloud costs
|
|
|
|
### Neutral
|
|
|
|
1. **Migration Effort** - Existing AgentDB deployments need migration (mitigated by dual-write adapter)
|
|
2. **Configuration Complexity** - More options to configure (mitigated by sensible defaults)
|
|
3. **Query Syntax** - Different query interface than AgentDB (mitigated by unified IMemoryBackend interface)
|
|
|
|
## Implementation Plan
|
|
|
|
### Phase 1: Core Plugin (Week 1-2)
|
|
- [x] Define plugin interface and types
|
|
- [ ] Implement ConnectionManager with pooling
|
|
- [ ] Implement QueryBuilder with parameterization
|
|
- [ ] Basic vector search (HNSW)
|
|
- [ ] Unit tests for core functionality
|
|
|
|
### Phase 2: Advanced Features (Week 3-4)
|
|
- [ ] Graph search with GNN layers
|
|
- [ ] Attention mechanism queries
|
|
- [ ] Hyperbolic embedding support
|
|
- [ ] Self-learning optimizer integration
|
|
- [ ] Integration tests
|
|
|
|
### Phase 3: MCP Tools & Migration (Week 5-6)
|
|
- [ ] MCP tool definitions
|
|
- [ ] Migration helper utilities
|
|
- [ ] Dual-write adapter
|
|
- [ ] Documentation
|
|
- [ ] Performance benchmarks
|
|
|
|
### Phase 4: Testing & Polish (Week 7-8)
|
|
- [ ] End-to-end tests
|
|
- [ ] Security audit
|
|
- [ ] Performance optimization
|
|
- [ ] CLI integration (`claude-flow memory --backend ruvector-postgres`)
|
|
- [ ] User documentation
|
|
|
|
## References
|
|
|
|
- **ADR-009**: Hybrid Memory Backend (AgentDB + SQLite)
|
|
- **ADR-015-v2**: Unified Plugin System
|
|
- **ADR-017**: RuVector Integration Architecture
|
|
- **ADR-006**: Unified Memory Service
|
|
- **@ruvector/postgres-cli**: https://github.com/ruvnet/ruvector-postgres
|
|
- **pgvector**: https://github.com/pgvector/pgvector
|
|
- **PostgreSQL**: https://www.postgresql.org/docs/
|
|
|
|
---
|
|
|
|
## Appendix A: SQL Function Reference
|
|
|
|
### Vector Operations
|
|
|
|
```sql
|
|
-- Cosine similarity search
|
|
SELECT id, content, 1 - (embedding <=> query_vector) AS similarity
|
|
FROM embeddings
|
|
WHERE embedding <=> query_vector < 0.3
|
|
ORDER BY embedding <=> query_vector
|
|
LIMIT 10;
|
|
|
|
-- Euclidean distance
|
|
SELECT id, embedding <-> query_vector AS distance FROM embeddings;
|
|
|
|
-- Inner product (dot product)
|
|
SELECT id, embedding <#> query_vector AS score FROM embeddings;
|
|
|
|
-- Bulk insert with COPY
|
|
COPY embeddings (id, content, embedding, metadata)
|
|
FROM STDIN WITH (FORMAT binary);
|
|
```
|
|
|
|
### Graph Operations
|
|
|
|
```sql
|
|
-- Create graph relationship
|
|
SELECT ruvector.add_edge('code_deps', $1, $2, $3);
|
|
|
|
-- GNN-enhanced search
|
|
SELECT * FROM ruvector.gnn_search(
|
|
query := $1,
|
|
graph := 'code_deps',
|
|
layers := ARRAY['GAT', 'GraphSAGE'],
|
|
k := 10
|
|
);
|
|
|
|
-- Subgraph extraction
|
|
SELECT * FROM ruvector.extract_subgraph('code_deps', $1, depth := 2);
|
|
```
|
|
|
|
### Attention Operations
|
|
|
|
```sql
|
|
-- Multi-head attention query
|
|
SELECT * FROM ruvector.attention_query(
|
|
query := $1,
|
|
documents := 'embeddings',
|
|
mechanism := 'multi_head',
|
|
heads := 8
|
|
);
|
|
|
|
-- Cross-attention between tables
|
|
SELECT * FROM ruvector.cross_attention(
|
|
queries := 'user_queries',
|
|
keys := 'document_embeddings',
|
|
values := 'document_content'
|
|
);
|
|
```
|
|
|
|
### Hyperbolic Operations
|
|
|
|
```sql
|
|
-- Poincare ball distance
|
|
SELECT ruvector.poincare_distance($1, $2, curvature := -1.0);
|
|
|
|
-- Hyperbolic centroid
|
|
SELECT ruvector.poincare_centroid(ARRAY[emb1, emb2, emb3]);
|
|
|
|
-- Hierarchical search
|
|
SELECT * FROM ruvector.hyperbolic_search(
|
|
query := $1,
|
|
model := 'poincare',
|
|
include_ancestors := true
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Appendix B: Configuration Examples
|
|
|
|
### Development Configuration
|
|
|
|
```typescript
|
|
const devConfig: RuVectorPostgresConfig = {
|
|
host: 'localhost',
|
|
port: 5432,
|
|
database: 'claude_flow_dev',
|
|
user: 'dev_user',
|
|
password: 'dev_password',
|
|
poolSize: 5,
|
|
enableLearning: false,
|
|
dimensions: 1536
|
|
};
|
|
```
|
|
|
|
### Production Configuration (AWS RDS)
|
|
|
|
```typescript
|
|
const prodConfig: RuVectorPostgresConfig = {
|
|
connectionString: process.env.DATABASE_URL,
|
|
ssl: {
|
|
rejectUnauthorized: true,
|
|
ca: fs.readFileSync('/etc/ssl/certs/rds-combined-ca-bundle.pem').toString()
|
|
},
|
|
poolSize: 20,
|
|
idleTimeout: 60000,
|
|
enableLearning: true,
|
|
learningConfig: {
|
|
learningRate: 0.01,
|
|
explorationFactor: 0.05,
|
|
minSamples: 10000
|
|
}
|
|
};
|
|
```
|
|
|
|
### High-Availability Configuration
|
|
|
|
```typescript
|
|
const haConfig: RuVectorPostgresConfig = {
|
|
// Primary for writes
|
|
primary: {
|
|
connectionString: process.env.PRIMARY_DATABASE_URL,
|
|
poolSize: 10
|
|
},
|
|
// Replicas for reads
|
|
replicas: [
|
|
{ connectionString: process.env.REPLICA_1_URL, poolSize: 20 },
|
|
{ connectionString: process.env.REPLICA_2_URL, poolSize: 20 }
|
|
],
|
|
loadBalancing: 'round-robin',
|
|
readFromReplicas: true
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
**Last Updated:** 2026-01-16
|
|
**Next Review:** 2026-02-16
|