Rebrand and personalize the bot as 'Bartender' - a companion for those who love deeply and feel intensely. Major changes: - Rename package: daemon_boyfriend -> loyal_companion - New default personality: Bartender - wise, steady, non-judgmental - Grief-aware system prompt (no toxic positivity, attachment-informed) - New relationship levels: New Face -> Close Friend progression - Bartender-style mood modifiers (steady presence) - New fact types: attachment_pattern, grief_context, coping_mechanism - Lower mood decay (0.05) for emotional stability - Higher fact extraction rate (0.4) - Bartender pays attention Updated all imports, configs, Docker files, and documentation.
263 lines
10 KiB
SQL
263 lines
10 KiB
SQL
-- Loyal Companion Database Schema
|
|
-- Run with: psql -U postgres -d loyal_companion -f schema.sql
|
|
|
|
-- Users table
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
discord_id BIGINT NOT NULL UNIQUE,
|
|
discord_username VARCHAR(255) NOT NULL,
|
|
discord_display_name VARCHAR(255),
|
|
custom_name VARCHAR(255),
|
|
first_seen_at TIMESTAMPTZ DEFAULT NOW(),
|
|
last_seen_at TIMESTAMPTZ DEFAULT NOW(),
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_users_discord_id ON users(discord_id);
|
|
CREATE INDEX IF NOT EXISTS ix_users_last_seen_at ON users(last_seen_at);
|
|
|
|
-- User preferences table
|
|
CREATE TABLE IF NOT EXISTS user_preferences (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
preference_key VARCHAR(100) NOT NULL,
|
|
preference_value TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(user_id, preference_key)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_user_preferences_user_id ON user_preferences(user_id);
|
|
|
|
-- User facts table
|
|
CREATE TABLE IF NOT EXISTS user_facts (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
fact_type VARCHAR(50) NOT NULL,
|
|
fact_content TEXT NOT NULL,
|
|
confidence FLOAT DEFAULT 1.0,
|
|
source VARCHAR(50) DEFAULT 'conversation',
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
learned_at TIMESTAMPTZ DEFAULT NOW(),
|
|
last_referenced_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_user_facts_user_id ON user_facts(user_id);
|
|
CREATE INDEX IF NOT EXISTS ix_user_facts_fact_type ON user_facts(fact_type);
|
|
CREATE INDEX IF NOT EXISTS ix_user_facts_is_active ON user_facts(is_active);
|
|
|
|
-- Guilds table
|
|
CREATE TABLE IF NOT EXISTS guilds (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
discord_id BIGINT NOT NULL UNIQUE,
|
|
name VARCHAR(255) NOT NULL,
|
|
joined_at TIMESTAMPTZ DEFAULT NOW(),
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
settings JSONB DEFAULT '{}',
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_guilds_discord_id ON guilds(discord_id);
|
|
|
|
-- Guild members table
|
|
CREATE TABLE IF NOT EXISTS guild_members (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
guild_id BIGINT NOT NULL REFERENCES guilds(id) ON DELETE CASCADE,
|
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
guild_nickname VARCHAR(255),
|
|
roles TEXT[],
|
|
joined_guild_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(guild_id, user_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_guild_members_guild_id ON guild_members(guild_id);
|
|
CREATE INDEX IF NOT EXISTS ix_guild_members_user_id ON guild_members(user_id);
|
|
|
|
-- Conversations table
|
|
CREATE TABLE IF NOT EXISTS conversations (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
guild_id BIGINT,
|
|
channel_id BIGINT,
|
|
started_at TIMESTAMPTZ DEFAULT NOW(),
|
|
last_message_at TIMESTAMPTZ DEFAULT NOW(),
|
|
message_count INTEGER DEFAULT 0,
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_conversations_user_id ON conversations(user_id);
|
|
CREATE INDEX IF NOT EXISTS ix_conversations_channel_id ON conversations(channel_id);
|
|
CREATE INDEX IF NOT EXISTS ix_conversations_last_message_at ON conversations(last_message_at);
|
|
CREATE INDEX IF NOT EXISTS ix_conversations_is_active ON conversations(is_active);
|
|
|
|
-- Messages table
|
|
CREATE TABLE IF NOT EXISTS messages (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
discord_message_id BIGINT,
|
|
role VARCHAR(20) NOT NULL,
|
|
content TEXT NOT NULL,
|
|
has_images BOOLEAN NOT NULL DEFAULT FALSE,
|
|
image_urls JSONB,
|
|
token_count INTEGER,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_messages_conversation_id ON messages(conversation_id);
|
|
CREATE INDEX IF NOT EXISTS ix_messages_user_id ON messages(user_id);
|
|
CREATE INDEX IF NOT EXISTS ix_messages_created_at ON messages(created_at);
|
|
|
|
-- =====================================================
|
|
-- LIVING AI TABLES
|
|
-- =====================================================
|
|
|
|
-- Bot state table (mood, statistics, preferences per guild)
|
|
CREATE TABLE IF NOT EXISTS bot_states (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
guild_id BIGINT UNIQUE, -- NULL = global state
|
|
mood_valence FLOAT DEFAULT 0.0, -- -1.0 (sad) to 1.0 (happy)
|
|
mood_arousal FLOAT DEFAULT 0.0, -- -1.0 (calm) to 1.0 (excited)
|
|
mood_updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
total_messages_sent INTEGER DEFAULT 0,
|
|
total_facts_learned INTEGER DEFAULT 0,
|
|
total_users_known INTEGER DEFAULT 0,
|
|
first_activated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
preferences JSONB DEFAULT '{}',
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_bot_states_guild_id ON bot_states(guild_id);
|
|
|
|
-- Bot opinions table (topic preferences)
|
|
CREATE TABLE IF NOT EXISTS bot_opinions (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
guild_id BIGINT, -- NULL = global opinion
|
|
topic VARCHAR(255) NOT NULL,
|
|
sentiment FLOAT DEFAULT 0.0, -- -1.0 to 1.0
|
|
interest_level FLOAT DEFAULT 0.5, -- 0.0 to 1.0
|
|
discussion_count INTEGER DEFAULT 0,
|
|
reasoning TEXT,
|
|
formed_at TIMESTAMPTZ DEFAULT NOW(),
|
|
last_reinforced_at TIMESTAMPTZ DEFAULT NOW(),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(guild_id, topic)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_bot_opinions_guild_id ON bot_opinions(guild_id);
|
|
CREATE INDEX IF NOT EXISTS ix_bot_opinions_topic ON bot_opinions(topic);
|
|
|
|
-- User relationships table (relationship depth tracking)
|
|
CREATE TABLE IF NOT EXISTS user_relationships (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
guild_id BIGINT, -- NULL = global relationship
|
|
relationship_score FLOAT DEFAULT 10.0, -- 0-100 scale
|
|
total_interactions INTEGER DEFAULT 0,
|
|
positive_interactions INTEGER DEFAULT 0,
|
|
negative_interactions INTEGER DEFAULT 0,
|
|
avg_message_length FLOAT DEFAULT 0.0,
|
|
conversation_depth_avg FLOAT DEFAULT 0.0,
|
|
shared_references JSONB DEFAULT '{}',
|
|
first_interaction_at TIMESTAMPTZ DEFAULT NOW(),
|
|
last_interaction_at TIMESTAMPTZ DEFAULT NOW(),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(user_id, guild_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_user_relationships_user_id ON user_relationships(user_id);
|
|
CREATE INDEX IF NOT EXISTS ix_user_relationships_guild_id ON user_relationships(guild_id);
|
|
|
|
-- User communication styles table (learned preferences)
|
|
CREATE TABLE IF NOT EXISTS user_communication_styles (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
|
|
preferred_length VARCHAR(20) DEFAULT 'medium', -- short/medium/long
|
|
preferred_formality FLOAT DEFAULT 0.5, -- 0=casual, 1=formal
|
|
emoji_affinity FLOAT DEFAULT 0.5, -- 0=none, 1=lots
|
|
humor_affinity FLOAT DEFAULT 0.5, -- 0=serious, 1=playful
|
|
detail_preference FLOAT DEFAULT 0.5, -- 0=concise, 1=detailed
|
|
engagement_signals JSONB DEFAULT '{}',
|
|
samples_collected INTEGER DEFAULT 0,
|
|
confidence FLOAT DEFAULT 0.0,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_user_communication_styles_user_id ON user_communication_styles(user_id);
|
|
|
|
-- Scheduled events table (proactive behavior)
|
|
CREATE TABLE IF NOT EXISTS scheduled_events (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
user_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
|
|
guild_id BIGINT,
|
|
channel_id BIGINT,
|
|
event_type VARCHAR(50) NOT NULL, -- birthday, follow_up, reminder, etc.
|
|
trigger_at TIMESTAMPTZ NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
context JSONB DEFAULT '{}',
|
|
is_recurring BOOLEAN DEFAULT FALSE,
|
|
recurrence_rule VARCHAR(100), -- yearly, monthly, etc.
|
|
status VARCHAR(20) DEFAULT 'pending', -- pending, triggered, cancelled
|
|
triggered_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_scheduled_events_user_id ON scheduled_events(user_id);
|
|
CREATE INDEX IF NOT EXISTS ix_scheduled_events_trigger_at ON scheduled_events(trigger_at);
|
|
CREATE INDEX IF NOT EXISTS ix_scheduled_events_status ON scheduled_events(status);
|
|
|
|
-- Fact associations table (cross-user memory links)
|
|
CREATE TABLE IF NOT EXISTS fact_associations (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
fact_id_1 BIGINT NOT NULL REFERENCES user_facts(id) ON DELETE CASCADE,
|
|
fact_id_2 BIGINT NOT NULL REFERENCES user_facts(id) ON DELETE CASCADE,
|
|
association_type VARCHAR(50) NOT NULL, -- shared_interest, same_location, etc.
|
|
strength FLOAT DEFAULT 0.5,
|
|
discovered_at TIMESTAMPTZ DEFAULT NOW(),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(fact_id_1, fact_id_2)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_fact_associations_fact_id_1 ON fact_associations(fact_id_1);
|
|
CREATE INDEX IF NOT EXISTS ix_fact_associations_fact_id_2 ON fact_associations(fact_id_2);
|
|
|
|
-- Mood history table (track mood changes over time)
|
|
CREATE TABLE IF NOT EXISTS mood_history (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
guild_id BIGINT,
|
|
valence FLOAT NOT NULL,
|
|
arousal FLOAT NOT NULL,
|
|
trigger_type VARCHAR(50) NOT NULL, -- conversation, time_decay, event
|
|
trigger_user_id BIGINT REFERENCES users(id) ON DELETE SET NULL,
|
|
trigger_description TEXT,
|
|
recorded_at TIMESTAMPTZ DEFAULT NOW(),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_mood_history_guild_id ON mood_history(guild_id);
|
|
CREATE INDEX IF NOT EXISTS ix_mood_history_recorded_at ON mood_history(recorded_at);
|
|
|
|
-- Add new columns to user_facts for enhanced memory
|
|
ALTER TABLE user_facts ADD COLUMN IF NOT EXISTS category VARCHAR(50);
|
|
ALTER TABLE user_facts ADD COLUMN IF NOT EXISTS importance FLOAT DEFAULT 0.5;
|
|
ALTER TABLE user_facts ADD COLUMN IF NOT EXISTS temporal_relevance VARCHAR(20);
|
|
ALTER TABLE user_facts ADD COLUMN IF NOT EXISTS expiry_date TIMESTAMPTZ;
|
|
ALTER TABLE user_facts ADD COLUMN IF NOT EXISTS extracted_from_message_id BIGINT;
|
|
ALTER TABLE user_facts ADD COLUMN IF NOT EXISTS extraction_context TEXT;
|