Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
# =============================================================================
# RuvBot - Google Cloud Build CI/CD Pipeline
# =============================================================================
# Trigger: Push to main branch or tag
# Deploy to: Cloud Run (serverless)
#
# Cost optimization:
# - Uses e2-standard-2 machine for builds
# - Caches npm dependencies
# - Multi-stage Docker builds
# =============================================================================
steps:
# ---------------------------------------------------------------------------
# Step 1: Build and Push Docker Image
# ---------------------------------------------------------------------------
- name: 'gcr.io/cloud-builders/docker'
id: 'build-image'
args:
- 'build'
- '-t'
- 'gcr.io/$PROJECT_ID/ruvbot:$COMMIT_SHA'
- '-t'
- 'gcr.io/$PROJECT_ID/ruvbot:latest'
- '-f'
- 'Dockerfile'
- '--cache-from'
- 'gcr.io/$PROJECT_ID/ruvbot:latest'
- '.'
# ---------------------------------------------------------------------------
# Step 2: Push to Container Registry
# ---------------------------------------------------------------------------
- name: 'gcr.io/cloud-builders/docker'
id: 'push-image'
args:
- 'push'
- '--all-tags'
- 'gcr.io/$PROJECT_ID/ruvbot'
# ---------------------------------------------------------------------------
# Step 3: Deploy to Cloud Run
# ---------------------------------------------------------------------------
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
id: 'deploy-cloud-run'
entrypoint: 'gcloud'
args:
- 'run'
- 'deploy'
- 'ruvbot'
- '--image'
- 'gcr.io/$PROJECT_ID/ruvbot:$COMMIT_SHA'
- '--region'
- '${_REGION}'
- '--platform'
- 'managed'
- '--allow-unauthenticated'
- '--port'
- '8080'
- '--memory'
- '512Mi'
- '--cpu'
- '1'
- '--min-instances'
- '0'
- '--max-instances'
- '10'
- '--timeout'
- '300'
- '--concurrency'
- '80'
- '--set-env-vars'
- 'NODE_ENV=production'
- '--set-secrets'
- 'ANTHROPIC_API_KEY=anthropic-api-key:latest,OPENROUTER_API_KEY=openrouter-api-key:latest,DATABASE_URL=database-url:latest'
- '--service-account'
- 'ruvbot-runner@$PROJECT_ID.iam.gserviceaccount.com'
# ---------------------------------------------------------------------------
# Step 4: Run Database Migrations (if needed)
# ---------------------------------------------------------------------------
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
id: 'run-migrations'
entrypoint: 'bash'
args:
- '-c'
- |
gcloud run jobs execute ruvbot-migrations \
--region ${_REGION} \
--wait \
|| echo "No migrations job configured, skipping"
# ---------------------------------------------------------------------------
# Substitutions (can be overridden)
# ---------------------------------------------------------------------------
substitutions:
_REGION: 'us-central1'
# ---------------------------------------------------------------------------
# Build Options
# ---------------------------------------------------------------------------
options:
logging: CLOUD_LOGGING_ONLY
machineType: 'E2_HIGHCPU_8'
dynamicSubstitutions: true
# ---------------------------------------------------------------------------
# Images to push
# ---------------------------------------------------------------------------
images:
- 'gcr.io/$PROJECT_ID/ruvbot:$COMMIT_SHA'
- 'gcr.io/$PROJECT_ID/ruvbot:latest'
# ---------------------------------------------------------------------------
# Timeout
# ---------------------------------------------------------------------------
timeout: '1200s'
# ---------------------------------------------------------------------------
# Tags for organization
# ---------------------------------------------------------------------------
tags:
- 'ruvbot'
- 'cloud-run'
- 'production'

View File

@@ -0,0 +1,263 @@
#!/bin/bash
# =============================================================================
# RuvBot - Google Cloud Platform Deployment Script
# =============================================================================
# Quick deployment for RuvBot to Google Cloud Run
#
# Usage:
# ./deploy.sh [options]
#
# Options:
# --project-id ID GCP Project ID (required)
# --region REGION GCP Region (default: us-central1)
# --env ENV Environment: dev, staging, prod (default: prod)
# --no-sql Skip Cloud SQL setup (use in-memory)
# --terraform Use Terraform instead of gcloud
# --destroy Destroy all resources
#
# Environment Variables:
# ANTHROPIC_API_KEY Required: Anthropic API key
# OPENROUTER_API_KEY Optional: OpenRouter API key
# =============================================================================
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Defaults
REGION="us-central1"
ENVIRONMENT="prod"
USE_TERRAFORM=false
ENABLE_SQL=true
DESTROY=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--project-id)
PROJECT_ID="$2"
shift 2
;;
--region)
REGION="$2"
shift 2
;;
--env)
ENVIRONMENT="$2"
shift 2
;;
--no-sql)
ENABLE_SQL=false
shift
;;
--terraform)
USE_TERRAFORM=true
shift
;;
--destroy)
DESTROY=true
shift
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
exit 1
;;
esac
done
# Validate required variables
if [ -z "$PROJECT_ID" ]; then
echo -e "${RED}Error: --project-id is required${NC}"
exit 1
fi
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo -e "${RED}Error: ANTHROPIC_API_KEY environment variable is required${NC}"
exit 1
fi
echo -e "${BLUE}================================================${NC}"
echo -e "${BLUE} RuvBot GCP Deployment${NC}"
echo -e "${BLUE}================================================${NC}"
echo -e "Project: ${GREEN}$PROJECT_ID${NC}"
echo -e "Region: ${GREEN}$REGION${NC}"
echo -e "Environment: ${GREEN}$ENVIRONMENT${NC}"
echo -e "Cloud SQL: ${GREEN}$ENABLE_SQL${NC}"
echo -e "Method: ${GREEN}$([ "$USE_TERRAFORM" = true ] && echo "Terraform" || echo "gcloud")${NC}"
echo -e "${BLUE}================================================${NC}"
# Confirm deployment
read -p "Continue with deployment? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Deployment cancelled."
exit 0
fi
# -----------------------------------------------------------------------------
# Terraform Deployment
# -----------------------------------------------------------------------------
if [ "$USE_TERRAFORM" = true ]; then
cd "$(dirname "$0")/terraform"
if [ "$DESTROY" = true ]; then
echo -e "${YELLOW}Destroying infrastructure...${NC}"
terraform destroy \
-var="project_id=$PROJECT_ID" \
-var="region=$REGION" \
-var="environment=$ENVIRONMENT" \
-var="enable_cloud_sql=$ENABLE_SQL" \
-var="anthropic_api_key=$ANTHROPIC_API_KEY" \
-var="openrouter_api_key=${OPENROUTER_API_KEY:-}"
exit 0
fi
echo -e "${YELLOW}Initializing Terraform...${NC}"
terraform init
echo -e "${YELLOW}Planning deployment...${NC}"
terraform plan \
-var="project_id=$PROJECT_ID" \
-var="region=$REGION" \
-var="environment=$ENVIRONMENT" \
-var="enable_cloud_sql=$ENABLE_SQL" \
-var="anthropic_api_key=$ANTHROPIC_API_KEY" \
-var="openrouter_api_key=${OPENROUTER_API_KEY:-}" \
-out=tfplan
echo -e "${YELLOW}Applying deployment...${NC}"
terraform apply tfplan
echo -e "${GREEN}Deployment complete!${NC}"
terraform output
exit 0
fi
# -----------------------------------------------------------------------------
# gcloud Deployment
# -----------------------------------------------------------------------------
echo -e "${YELLOW}Setting project...${NC}"
gcloud config set project "$PROJECT_ID"
echo -e "${YELLOW}Enabling APIs...${NC}"
gcloud services enable \
run.googleapis.com \
cloudbuild.googleapis.com \
secretmanager.googleapis.com \
sqladmin.googleapis.com \
storage.googleapis.com
# Create secrets
echo -e "${YELLOW}Creating secrets...${NC}"
echo -n "$ANTHROPIC_API_KEY" | gcloud secrets create anthropic-api-key \
--data-file=- --replication-policy=automatic 2>/dev/null || \
echo -n "$ANTHROPIC_API_KEY" | gcloud secrets versions add anthropic-api-key --data-file=-
if [ -n "$OPENROUTER_API_KEY" ]; then
echo -n "$OPENROUTER_API_KEY" | gcloud secrets create openrouter-api-key \
--data-file=- --replication-policy=automatic 2>/dev/null || \
echo -n "$OPENROUTER_API_KEY" | gcloud secrets versions add openrouter-api-key --data-file=-
fi
# Create service account
echo -e "${YELLOW}Creating service account...${NC}"
gcloud iam service-accounts create ruvbot-runner \
--display-name="RuvBot Cloud Run" 2>/dev/null || true
SA_EMAIL="ruvbot-runner@$PROJECT_ID.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:$SA_EMAIL" \
--role="roles/secretmanager.secretAccessor" --quiet
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:$SA_EMAIL" \
--role="roles/cloudsql.client" --quiet
# Create Cloud SQL (if enabled)
if [ "$ENABLE_SQL" = true ]; then
echo -e "${YELLOW}Creating Cloud SQL instance...${NC}"
if ! gcloud sql instances describe "ruvbot-$ENVIRONMENT" --quiet 2>/dev/null; then
gcloud sql instances create "ruvbot-$ENVIRONMENT" \
--database-version=POSTGRES_16 \
--tier=db-f1-micro \
--region="$REGION" \
--storage-size=10GB \
--storage-auto-increase \
--availability-type=zonal
# Generate password
DB_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=' | head -c 24)
# Create database and user
gcloud sql databases create ruvbot --instance="ruvbot-$ENVIRONMENT"
gcloud sql users create ruvbot --instance="ruvbot-$ENVIRONMENT" --password="$DB_PASSWORD"
# Get instance IP
INSTANCE_IP=$(gcloud sql instances describe "ruvbot-$ENVIRONMENT" --format='get(ipAddresses[0].ipAddress)')
# Store connection string in secrets
DATABASE_URL="postgresql://ruvbot:$DB_PASSWORD@$INSTANCE_IP:5432/ruvbot"
echo -n "$DATABASE_URL" | gcloud secrets create database-url \
--data-file=- --replication-policy=automatic 2>/dev/null || \
echo -n "$DATABASE_URL" | gcloud secrets versions add database-url --data-file=-
fi
fi
# Build and push image
echo -e "${YELLOW}Building container image...${NC}"
cd "$(dirname "$0")/../.."
gcloud builds submit --tag "gcr.io/$PROJECT_ID/ruvbot:latest" .
# Deploy to Cloud Run
echo -e "${YELLOW}Deploying to Cloud Run...${NC}"
SECRETS="ANTHROPIC_API_KEY=anthropic-api-key:latest"
if [ -n "$OPENROUTER_API_KEY" ]; then
SECRETS="$SECRETS,OPENROUTER_API_KEY=openrouter-api-key:latest"
fi
if [ "$ENABLE_SQL" = true ]; then
SECRETS="$SECRETS,DATABASE_URL=database-url:latest"
fi
gcloud run deploy ruvbot \
--image="gcr.io/$PROJECT_ID/ruvbot:latest" \
--region="$REGION" \
--platform=managed \
--allow-unauthenticated \
--port=8080 \
--memory=512Mi \
--cpu=1 \
--min-instances=0 \
--max-instances=10 \
--timeout=300 \
--concurrency=80 \
--set-env-vars="NODE_ENV=production" \
--set-secrets="$SECRETS" \
--service-account="$SA_EMAIL"
# Get URL
SERVICE_URL=$(gcloud run services describe ruvbot --region="$REGION" --format='get(status.url)')
echo ""
echo -e "${GREEN}================================================${NC}"
echo -e "${GREEN} Deployment Complete!${NC}"
echo -e "${GREEN}================================================${NC}"
echo -e "Service URL: ${BLUE}$SERVICE_URL${NC}"
echo -e "Health Check: ${BLUE}$SERVICE_URL/health${NC}"
echo ""
echo -e "${YELLOW}Estimated Monthly Cost:${NC}"
echo " - Cloud Run: ~\$0 (free tier)"
if [ "$ENABLE_SQL" = true ]; then
echo " - Cloud SQL: ~\$10-15/month"
fi
echo " - Secrets: ~\$0.18/month"
echo " - Total: ~\$$([ "$ENABLE_SQL" = true ] && echo "15-20" || echo "5")/month"
echo ""
echo -e "${GREEN}Done!${NC}"

View File

@@ -0,0 +1,443 @@
# =============================================================================
# RuvBot - Google Cloud Platform Infrastructure
# =============================================================================
# Cost-optimized deployment using:
# - Cloud Run (serverless, pay-per-use)
# - Cloud SQL PostgreSQL (smallest instance, can scale)
# - Memorystore Redis (optional, can use in-memory)
# - Secret Manager (for credentials)
# - Cloud Storage (for file uploads)
#
# Estimated monthly cost (low traffic): $15-30/month
# - Cloud Run: ~$0 (generous free tier)
# - Cloud SQL: ~$10-15/month (db-f1-micro)
# - Secret Manager: ~$0.06/secret/month
# - Cloud Storage: ~$0.02/GB/month
# =============================================================================
terraform {
required_version = ">= 1.5.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 5.0"
}
}
# Uncomment for remote state (recommended for production)
# backend "gcs" {
# bucket = "your-terraform-state-bucket"
# prefix = "ruvbot/state"
# }
}
# -----------------------------------------------------------------------------
# Variables
# -----------------------------------------------------------------------------
variable "project_id" {
description = "GCP Project ID"
type = string
}
variable "region" {
description = "GCP Region"
type = string
default = "us-central1"
}
variable "environment" {
description = "Environment (dev, staging, prod)"
type = string
default = "prod"
}
variable "enable_cloud_sql" {
description = "Enable Cloud SQL PostgreSQL (adds ~$10/month)"
type = bool
default = true
}
variable "enable_redis" {
description = "Enable Memorystore Redis (adds ~$30/month)"
type = bool
default = false # Disabled by default for cost savings
}
variable "anthropic_api_key" {
description = "Anthropic API Key"
type = string
sensitive = true
}
variable "openrouter_api_key" {
description = "OpenRouter API Key"
type = string
sensitive = true
default = ""
}
# -----------------------------------------------------------------------------
# Provider Configuration
# -----------------------------------------------------------------------------
provider "google" {
project = var.project_id
region = var.region
}
provider "google-beta" {
project = var.project_id
region = var.region
}
# -----------------------------------------------------------------------------
# Enable Required APIs
# -----------------------------------------------------------------------------
resource "google_project_service" "services" {
for_each = toset([
"run.googleapis.com",
"cloudbuild.googleapis.com",
"secretmanager.googleapis.com",
"sqladmin.googleapis.com",
"storage.googleapis.com",
"redis.googleapis.com",
"vpcaccess.googleapis.com",
])
service = each.value
disable_on_destroy = false
}
# -----------------------------------------------------------------------------
# Service Account for Cloud Run
# -----------------------------------------------------------------------------
resource "google_service_account" "ruvbot_runner" {
account_id = "ruvbot-runner"
display_name = "RuvBot Cloud Run Service Account"
description = "Service account for RuvBot Cloud Run service"
}
resource "google_project_iam_member" "ruvbot_runner_roles" {
for_each = toset([
"roles/secretmanager.secretAccessor",
"roles/cloudsql.client",
"roles/storage.objectAdmin",
"roles/logging.logWriter",
"roles/monitoring.metricWriter",
])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.ruvbot_runner.email}"
}
# -----------------------------------------------------------------------------
# Secret Manager - Store API Keys Securely
# -----------------------------------------------------------------------------
resource "google_secret_manager_secret" "anthropic_api_key" {
secret_id = "anthropic-api-key"
replication {
auto {}
}
depends_on = [google_project_service.services]
}
resource "google_secret_manager_secret_version" "anthropic_api_key" {
secret = google_secret_manager_secret.anthropic_api_key.id
secret_data = var.anthropic_api_key
}
resource "google_secret_manager_secret" "openrouter_api_key" {
count = var.openrouter_api_key != "" ? 1 : 0
secret_id = "openrouter-api-key"
replication {
auto {}
}
depends_on = [google_project_service.services]
}
resource "google_secret_manager_secret_version" "openrouter_api_key" {
count = var.openrouter_api_key != "" ? 1 : 0
secret = google_secret_manager_secret.openrouter_api_key[0].id
secret_data = var.openrouter_api_key
}
# -----------------------------------------------------------------------------
# Cloud SQL PostgreSQL (Cost-Optimized)
# -----------------------------------------------------------------------------
resource "google_sql_database_instance" "ruvbot" {
count = var.enable_cloud_sql ? 1 : 0
name = "ruvbot-${var.environment}"
database_version = "POSTGRES_16"
region = var.region
settings {
# db-f1-micro: 0.6GB RAM, shared CPU - ~$10/month
tier = "db-f1-micro"
availability_type = "ZONAL" # Single zone for cost savings
disk_size = 10 # Minimum 10GB
disk_type = "PD_SSD"
disk_autoresize = true
backup_configuration {
enabled = true
point_in_time_recovery_enabled = false # Disable for cost savings
backup_retention_settings {
retained_backups = 7
}
}
ip_configuration {
ipv4_enabled = true
# For production, use private IP with VPC connector
authorized_networks {
name = "allow-cloud-run"
value = "0.0.0.0/0" # Cloud Run uses public IP; restrict in production
}
}
database_flags {
name = "max_connections"
value = "50"
}
}
deletion_protection = var.environment == "prod"
depends_on = [google_project_service.services]
}
resource "google_sql_database" "ruvbot" {
count = var.enable_cloud_sql ? 1 : 0
name = "ruvbot"
instance = google_sql_database_instance.ruvbot[0].name
}
resource "google_sql_user" "ruvbot" {
count = var.enable_cloud_sql ? 1 : 0
name = "ruvbot"
instance = google_sql_database_instance.ruvbot[0].name
password = random_password.db_password[0].result
}
resource "random_password" "db_password" {
count = var.enable_cloud_sql ? 1 : 0
length = 32
special = false
}
resource "google_secret_manager_secret" "database_url" {
count = var.enable_cloud_sql ? 1 : 0
secret_id = "database-url"
replication {
auto {}
}
depends_on = [google_project_service.services]
}
resource "google_secret_manager_secret_version" "database_url" {
count = var.enable_cloud_sql ? 1 : 0
secret = google_secret_manager_secret.database_url[0].id
secret_data = "postgresql://ruvbot:${random_password.db_password[0].result}@${google_sql_database_instance.ruvbot[0].public_ip_address}:5432/ruvbot"
}
# -----------------------------------------------------------------------------
# Cloud Storage Bucket (for file uploads)
# -----------------------------------------------------------------------------
resource "google_storage_bucket" "ruvbot_data" {
name = "${var.project_id}-ruvbot-data"
location = var.region
force_destroy = var.environment != "prod"
uniform_bucket_level_access = true
lifecycle_rule {
condition {
age = 30
}
action {
type = "SetStorageClass"
storage_class = "NEARLINE"
}
}
lifecycle_rule {
condition {
age = 90
}
action {
type = "SetStorageClass"
storage_class = "COLDLINE"
}
}
versioning {
enabled = var.environment == "prod"
}
}
# -----------------------------------------------------------------------------
# Cloud Run Service
# -----------------------------------------------------------------------------
resource "google_cloud_run_v2_service" "ruvbot" {
name = "ruvbot"
location = var.region
ingress = "INGRESS_TRAFFIC_ALL"
template {
service_account = google_service_account.ruvbot_runner.email
scaling {
min_instance_count = 0 # Scale to zero when not in use
max_instance_count = 10
}
containers {
image = "gcr.io/${var.project_id}/ruvbot:latest"
ports {
container_port = 8080
}
resources {
limits = {
cpu = "1"
memory = "512Mi"
}
cpu_idle = true # Reduce cost during idle
startup_cpu_boost = true # Faster cold starts
}
env {
name = "NODE_ENV"
value = "production"
}
env {
name = "GCS_BUCKET"
value = google_storage_bucket.ruvbot_data.name
}
env {
name = "ANTHROPIC_API_KEY"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.anthropic_api_key.secret_id
version = "latest"
}
}
}
dynamic "env" {
for_each = var.enable_cloud_sql ? [1] : []
content {
name = "DATABASE_URL"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.database_url[0].secret_id
version = "latest"
}
}
}
}
startup_probe {
http_get {
path = "/health"
port = 8080
}
initial_delay_seconds = 5
timeout_seconds = 3
period_seconds = 10
failure_threshold = 3
}
liveness_probe {
http_get {
path = "/health"
port = 8080
}
timeout_seconds = 3
period_seconds = 30
failure_threshold = 3
}
}
max_instance_request_concurrency = 80
timeout = "300s"
}
traffic {
type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
percent = 100
}
depends_on = [
google_project_service.services,
google_secret_manager_secret_version.anthropic_api_key,
]
}
# -----------------------------------------------------------------------------
# Allow Unauthenticated Access to Cloud Run
# -----------------------------------------------------------------------------
resource "google_cloud_run_v2_service_iam_member" "public_access" {
project = var.project_id
location = var.region
name = google_cloud_run_v2_service.ruvbot.name
role = "roles/run.invoker"
member = "allUsers"
}
# -----------------------------------------------------------------------------
# Outputs
# -----------------------------------------------------------------------------
output "cloud_run_url" {
description = "Cloud Run service URL"
value = google_cloud_run_v2_service.ruvbot.uri
}
output "cloud_sql_connection_name" {
description = "Cloud SQL connection name"
value = var.enable_cloud_sql ? google_sql_database_instance.ruvbot[0].connection_name : "N/A"
}
output "storage_bucket" {
description = "Cloud Storage bucket name"
value = google_storage_bucket.ruvbot_data.name
}
output "estimated_monthly_cost" {
description = "Estimated monthly cost"
value = <<-EOT
Estimated Monthly Cost (low traffic):
- Cloud Run: ~$0 (free tier covers ~2M requests)
- Cloud SQL: ${var.enable_cloud_sql ? "~$10-15" : "$0"}
- Secret Manager: ~$0.18 (3 secrets)
- Cloud Storage: ~$0.02/GB
- Redis: ${var.enable_redis ? "~$30" : "$0 (disabled)"}
----------------------------------------
Total: ~$${var.enable_cloud_sql ? (var.enable_redis ? "45-50" : "15-20") : "5-10"}/month
EOT
}

View File

@@ -0,0 +1,310 @@
-- =============================================================================
-- 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;