Files

311 lines
12 KiB
PL/PgSQL

-- =============================================================================
-- RuvBot - Database Initialization Script
-- =============================================================================
-- PostgreSQL schema for multi-tenant RuvBot deployment
-- Supports: Sessions, Memory, Skills, Events, Metrics
-- =============================================================================
-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- For text search
-- =============================================================================
-- Tenants (Multi-tenancy support)
-- =============================================================================
CREATE TABLE IF NOT EXISTS tenants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL,
settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Enable Row-Level Security
ALTER TABLE tenants ENABLE ROW LEVEL SECURITY;
-- =============================================================================
-- Users
-- =============================================================================
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
external_id VARCHAR(255), -- Slack user ID, Discord ID, etc.
username VARCHAR(255),
email VARCHAR(255),
avatar_url TEXT,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE(tenant_id, external_id)
);
CREATE INDEX idx_users_tenant ON users(tenant_id);
CREATE INDEX idx_users_external ON users(external_id);
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- =============================================================================
-- Agents
-- =============================================================================
CREATE TABLE IF NOT EXISTS agents (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(50) DEFAULT 'assistant',
model VARCHAR(100) DEFAULT 'claude-3-5-sonnet',
system_prompt TEXT,
config JSONB DEFAULT '{}',
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_agents_tenant ON agents(tenant_id);
ALTER TABLE agents ENABLE ROW LEVEL SECURITY;
-- =============================================================================
-- Sessions
-- =============================================================================
CREATE TABLE IF NOT EXISTS sessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
agent_id UUID REFERENCES agents(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
channel_type VARCHAR(50), -- slack, discord, telegram, web
channel_id VARCHAR(255), -- Channel/room ID
thread_id VARCHAR(255), -- Thread ID if applicable
status VARCHAR(20) DEFAULT 'active',
metadata JSONB DEFAULT '{}',
started_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
ended_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_sessions_tenant ON sessions(tenant_id);
CREATE INDEX idx_sessions_agent ON sessions(agent_id);
CREATE INDEX idx_sessions_user ON sessions(user_id);
CREATE INDEX idx_sessions_channel ON sessions(channel_type, channel_id);
CREATE INDEX idx_sessions_status ON sessions(status) WHERE status = 'active';
ALTER TABLE sessions ENABLE ROW LEVEL SECURITY;
-- =============================================================================
-- Turns (Conversation Messages)
-- =============================================================================
CREATE TABLE IF NOT EXISTS turns (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
session_id UUID REFERENCES sessions(id) ON DELETE CASCADE,
role VARCHAR(20) NOT NULL, -- user, assistant, system
content TEXT NOT NULL,
tokens_input INTEGER DEFAULT 0,
tokens_output INTEGER DEFAULT 0,
latency_ms INTEGER,
tool_calls JSONB,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_turns_session ON turns(session_id);
CREATE INDEX idx_turns_created ON turns(created_at DESC);
-- =============================================================================
-- Memory (Long-term storage with vector search)
-- =============================================================================
CREATE TABLE IF NOT EXISTS memories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, -- fact, preference, episode, semantic
content TEXT NOT NULL,
embedding FLOAT8[], -- Vector embedding (1536 dimensions for OpenAI, 768 for local)
importance FLOAT DEFAULT 0.5,
access_count INTEGER DEFAULT 0,
last_accessed TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_memories_tenant ON memories(tenant_id);
CREATE INDEX idx_memories_user ON memories(user_id);
CREATE INDEX idx_memories_type ON memories(type);
CREATE INDEX idx_memories_importance ON memories(importance DESC);
-- Full-text search index
CREATE INDEX idx_memories_content_trgm ON memories USING gin(content gin_trgm_ops);
ALTER TABLE memories ENABLE ROW LEVEL SECURITY;
-- =============================================================================
-- Skills
-- =============================================================================
CREATE TABLE IF NOT EXISTS skills (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
version VARCHAR(20) DEFAULT '1.0.0',
schema JSONB NOT NULL, -- Input/output schema
implementation JSONB, -- Skill configuration
enabled BOOLEAN DEFAULT true,
usage_count INTEGER DEFAULT 0,
success_rate FLOAT DEFAULT 1.0,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE(tenant_id, name, version)
);
CREATE INDEX idx_skills_tenant ON skills(tenant_id);
CREATE INDEX idx_skills_category ON skills(category);
CREATE INDEX idx_skills_enabled ON skills(enabled) WHERE enabled = true;
ALTER TABLE skills ENABLE ROW LEVEL SECURITY;
-- =============================================================================
-- Events (Event Sourcing)
-- =============================================================================
CREATE TABLE IF NOT EXISTS events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
aggregate_type VARCHAR(100) NOT NULL,
aggregate_id UUID NOT NULL,
event_type VARCHAR(100) NOT NULL,
event_version INTEGER DEFAULT 1,
payload JSONB NOT NULL,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_events_aggregate ON events(aggregate_type, aggregate_id);
CREATE INDEX idx_events_type ON events(event_type);
CREATE INDEX idx_events_created ON events(created_at DESC);
-- =============================================================================
-- Metrics (Usage tracking)
-- =============================================================================
CREATE TABLE IF NOT EXISTS metrics (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
metric_name VARCHAR(100) NOT NULL,
metric_value FLOAT NOT NULL,
dimensions JSONB DEFAULT '{}',
recorded_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_metrics_tenant ON metrics(tenant_id);
CREATE INDEX idx_metrics_name ON metrics(metric_name);
CREATE INDEX idx_metrics_recorded ON metrics(recorded_at DESC);
-- Partition by time for better performance
-- (In production, consider using TimescaleDB or partitioning)
-- =============================================================================
-- Patterns (Learning patterns from interactions)
-- =============================================================================
CREATE TABLE IF NOT EXISTS patterns (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
pattern_type VARCHAR(50) NOT NULL, -- intent, response, workflow
pattern_key VARCHAR(255) NOT NULL,
pattern_value JSONB NOT NULL,
confidence FLOAT DEFAULT 0.5,
usage_count INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE(tenant_id, pattern_type, pattern_key)
);
CREATE INDEX idx_patterns_tenant ON patterns(tenant_id);
CREATE INDEX idx_patterns_type ON patterns(pattern_type);
CREATE INDEX idx_patterns_confidence ON patterns(confidence DESC);
ALTER TABLE patterns ENABLE ROW LEVEL SECURITY;
-- =============================================================================
-- Functions
-- =============================================================================
-- Update timestamp trigger
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Apply to all tables with updated_at
DO $$
DECLARE
t text;
BEGIN
FOR t IN
SELECT table_name
FROM information_schema.columns
WHERE column_name = 'updated_at'
AND table_schema = 'public'
LOOP
EXECUTE format('
DROP TRIGGER IF EXISTS update_%I_updated_at ON %I;
CREATE TRIGGER update_%I_updated_at
BEFORE UPDATE ON %I
FOR EACH ROW
EXECUTE FUNCTION update_updated_at();
', t, t, t, t);
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- =============================================================================
-- Default Data
-- =============================================================================
-- Default tenant for development
INSERT INTO tenants (id, name, slug, settings)
VALUES (
'00000000-0000-0000-0000-000000000001',
'Default Tenant',
'default',
'{"plan": "free", "features": ["basic_chat", "memory", "skills"]}'
)
ON CONFLICT (slug) DO NOTHING;
-- Default agent
INSERT INTO agents (id, tenant_id, name, type, model, system_prompt, config)
VALUES (
'00000000-0000-0000-0000-000000000001',
'00000000-0000-0000-0000-000000000001',
'RuvBot',
'assistant',
'claude-3-5-sonnet',
'You are RuvBot, a helpful AI assistant with long-term memory and learning capabilities.',
'{"temperature": 0.7, "maxTokens": 4096}'
)
ON CONFLICT DO NOTHING;
-- =============================================================================
-- Grants (for application user)
-- =============================================================================
-- Note: Run these after creating the application user
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ruvbot;
-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ruvbot;
-- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO ruvbot;