-- Migration 046: Memory Service Full Schema -- Повна схема пам'яті для агентів (episodic, semantic, group identity) -- Дата: 2026-01-17 -- Автор: DAARION Team -- ============================================================================ -- 1. GROUPS — Групові чати -- ============================================================================ CREATE TABLE IF NOT EXISTS groups ( group_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), platform VARCHAR(50) NOT NULL, -- telegram, discord, matrix, slack platform_group_id VARCHAR(255) NOT NULL, -- ID групи на платформі name VARCHAR(255), description TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), metadata JSONB DEFAULT '{}', UNIQUE(platform, platform_group_id) ); CREATE INDEX idx_groups_platform ON groups(platform); CREATE INDEX idx_groups_platform_id ON groups(platform, platform_group_id); -- ============================================================================ -- 2. GROUP_MEMBERS — Учасники груп -- ============================================================================ CREATE TABLE IF NOT EXISTS group_members ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), group_id UUID NOT NULL REFERENCES groups(group_id) ON DELETE CASCADE, platform_user_id VARCHAR(255) NOT NULL, -- Стабільний ID користувача на платформі nickname VARCHAR(255), -- Поточний нікнейм (може змінюватись) first_seen_at TIMESTAMPTZ DEFAULT NOW(), last_seen_at TIMESTAMPTZ DEFAULT NOW(), last_message_at TIMESTAMPTZ, message_count INTEGER DEFAULT 0, no_memory_in_group BOOLEAN DEFAULT FALSE, -- Opt-out flag status VARCHAR(20) DEFAULT 'active', -- active, left, banned UNIQUE(group_id, platform_user_id) ); CREATE INDEX idx_group_members_group ON group_members(group_id); CREATE INDEX idx_group_members_user ON group_members(platform_user_id); CREATE INDEX idx_group_members_no_memory ON group_members(no_memory_in_group) WHERE no_memory_in_group = TRUE; -- ============================================================================ -- 3. GROUP_MEMBER_PROFILES — Профілі учасників у групах -- ============================================================================ CREATE TABLE IF NOT EXISTS group_member_profiles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), group_id UUID NOT NULL REFERENCES groups(group_id) ON DELETE CASCADE, platform_user_id VARCHAR(255) NOT NULL, -- Роль/контекст (виключно для цієї групи) role_hint VARCHAR(100), -- investor, engineer, moderator, newcomer, etc. language_preference VARCHAR(10) DEFAULT 'uk', communication_style VARCHAR(50), -- formal, casual, technical -- Інтереси/теми (в контексті групи) topics_of_interest TEXT[], -- ['BioMiner', 'tokenomics', 'governance'] last_topics JSONB DEFAULT '[]', -- Останні обговорювані теми -- Преференції спілкування preferences_json JSONB DEFAULT '{}', notes_short TEXT, -- Короткі нотатки агента про учасника created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(group_id, platform_user_id) ); CREATE INDEX idx_group_profiles_group ON group_member_profiles(group_id); CREATE INDEX idx_group_profiles_user ON group_member_profiles(platform_user_id); CREATE INDEX idx_group_profiles_role ON group_member_profiles(role_hint); -- ============================================================================ -- 4. USERS — Глобальні користувачі (для DM та cross-platform) -- ============================================================================ CREATE TABLE IF NOT EXISTS memory_users ( user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Платформенні ідентифікатори telegram_id VARCHAR(50), discord_id VARCHAR(50), matrix_id VARCHAR(255), email VARCHAR(255), -- Глобальні налаштування display_name VARCHAR(255), global_memory_enabled BOOLEAN DEFAULT TRUE, pii_allowed BOOLEAN DEFAULT FALSE, -- Дозвіл на збереження PII created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(telegram_id), UNIQUE(discord_id), UNIQUE(matrix_id) ); CREATE INDEX idx_users_telegram ON memory_users(telegram_id) WHERE telegram_id IS NOT NULL; CREATE INDEX idx_users_discord ON memory_users(discord_id) WHERE discord_id IS NOT NULL; -- ============================================================================ -- 5. CONSENT — Згода на обробку пам'яті -- ============================================================================ CREATE TABLE IF NOT EXISTS memory_consent ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES memory_users(user_id) ON DELETE CASCADE, platform_user_id VARCHAR(255), -- Якщо user_id ще не створено -- Типи згоди memory_enabled BOOLEAN DEFAULT TRUE, pii_allowed BOOLEAN DEFAULT FALSE, cross_group_memory BOOLEAN DEFAULT FALSE, -- Дозвіл на перенос між групами dm_memory_enabled BOOLEAN DEFAULT TRUE, -- Retention policy retention_policy VARCHAR(50) DEFAULT 'default', -- default, minimal, extended, forever retention_days INTEGER DEFAULT 365, updated_at TIMESTAMPTZ DEFAULT NOW(), updated_by VARCHAR(100) DEFAULT 'user', UNIQUE(user_id), UNIQUE(platform_user_id) ); -- ============================================================================ -- 6. CONVERSATIONS — Розмови/сесії -- ============================================================================ CREATE TABLE IF NOT EXISTS conversations ( conversation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Контекст user_id UUID REFERENCES memory_users(user_id), platform_user_id VARCHAR(255), -- Fallback якщо немає user_id group_id UUID REFERENCES groups(group_id), -- Метадані channel VARCHAR(50) NOT NULL, -- dm, group, channel platform VARCHAR(50) NOT NULL, agent_id VARCHAR(100) DEFAULT 'helion', -- Часові межі started_at TIMESTAMPTZ DEFAULT NOW(), ended_at TIMESTAMPTZ, last_message_at TIMESTAMPTZ DEFAULT NOW(), -- Статистика message_count INTEGER DEFAULT 0, token_count INTEGER DEFAULT 0, -- Стан status VARCHAR(20) DEFAULT 'active', -- active, ended, archived summary TEXT, -- Автоматичне резюме після завершення metadata JSONB DEFAULT '{}' ); CREATE INDEX idx_conversations_user ON conversations(user_id); CREATE INDEX idx_conversations_platform_user ON conversations(platform_user_id); CREATE INDEX idx_conversations_group ON conversations(group_id); CREATE INDEX idx_conversations_status ON conversations(status); CREATE INDEX idx_conversations_started ON conversations(started_at DESC); -- ============================================================================ -- 7. MESSAGES — Повідомлення -- ============================================================================ CREATE TABLE IF NOT EXISTS messages ( message_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id UUID NOT NULL REFERENCES conversations(conversation_id) ON DELETE CASCADE, -- Автор role VARCHAR(20) NOT NULL, -- user, assistant, system, tool platform_user_id VARCHAR(255), -- Для групових чатів -- Контент content TEXT NOT NULL, content_type VARCHAR(50) DEFAULT 'text', -- text, image, audio, file -- Часова мітка created_at TIMESTAMPTZ DEFAULT NOW(), -- PII/Redaction has_pii BOOLEAN DEFAULT FALSE, redaction_state VARCHAR(20) DEFAULT 'none', -- none, partial, full original_content_hash VARCHAR(64), -- Для аудиту -- Токени token_count INTEGER, model_used VARCHAR(100), metadata JSONB DEFAULT '{}' ); CREATE INDEX idx_messages_conversation ON messages(conversation_id); CREATE INDEX idx_messages_created ON messages(created_at DESC); CREATE INDEX idx_messages_role ON messages(role); CREATE INDEX idx_messages_pii ON messages(has_pii) WHERE has_pii = TRUE; -- ============================================================================ -- 8. MEMORIES — Довготривала пам'ять (episodic + semantic) -- ============================================================================ CREATE TABLE IF NOT EXISTS memories ( memory_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Власник пам'яті user_id UUID REFERENCES memory_users(user_id), platform_user_id VARCHAR(255), group_id UUID REFERENCES groups(group_id), -- NULL = глобальна/DM пам'ять -- Тип пам'яті memory_type VARCHAR(50) NOT NULL, -- episodic, semantic, procedural category VARCHAR(100), -- preference, fact, interaction, topic_interest -- Контент content TEXT NOT NULL, summary TEXT, -- Короткий опис для швидкого retrieval -- Важливість та TTL importance FLOAT DEFAULT 0.5, -- 0.0 - 1.0 confidence FLOAT DEFAULT 0.8, ttl_days INTEGER, -- NULL = безстроково expires_at TIMESTAMPTZ, -- Джерело source_message_ids UUID[], source_conversation_id UUID REFERENCES conversations(conversation_id), extraction_method VARCHAR(50) DEFAULT 'explicit', -- explicit, inferred, llm_extracted -- Embedding (для vector search) embedding_id VARCHAR(255), -- ID в Qdrant/pgvector embedding_model VARCHAR(100), -- Часові мітки created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), last_accessed_at TIMESTAMPTZ, access_count INTEGER DEFAULT 0, -- Статус is_active BOOLEAN DEFAULT TRUE, is_verified BOOLEAN DEFAULT FALSE, metadata JSONB DEFAULT '{}' ); CREATE INDEX idx_memories_user ON memories(user_id); CREATE INDEX idx_memories_platform_user ON memories(platform_user_id); CREATE INDEX idx_memories_group ON memories(group_id); CREATE INDEX idx_memories_type ON memories(memory_type); CREATE INDEX idx_memories_category ON memories(category); CREATE INDEX idx_memories_importance ON memories(importance DESC); CREATE INDEX idx_memories_active ON memories(is_active) WHERE is_active = TRUE; CREATE INDEX idx_memories_expires ON memories(expires_at) WHERE expires_at IS NOT NULL; -- ============================================================================ -- 9. MEMORY_EVENTS — Аудит (хто/що/коли змінював пам'ять) -- ============================================================================ CREATE TABLE IF NOT EXISTS memory_events ( event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Що змінилось memory_id UUID REFERENCES memories(memory_id) ON DELETE SET NULL, user_id UUID REFERENCES memory_users(user_id), group_id UUID REFERENCES groups(group_id), -- Дія action VARCHAR(50) NOT NULL, -- created, updated, deleted, accessed, opt_out, opt_in -- Хто зробив actor VARCHAR(100) NOT NULL, -- user, agent:helion, system, admin actor_user_id VARCHAR(255), -- Деталі old_value JSONB, new_value JSONB, reason TEXT, -- Час created_at TIMESTAMPTZ DEFAULT NOW(), -- Контекст ip_address INET, user_agent TEXT, metadata JSONB DEFAULT '{}' ); CREATE INDEX idx_memory_events_memory ON memory_events(memory_id); CREATE INDEX idx_memory_events_user ON memory_events(user_id); CREATE INDEX idx_memory_events_action ON memory_events(action); CREATE INDEX idx_memory_events_created ON memory_events(created_at DESC); -- ============================================================================ -- 10. GROUP_INTERACTIONS — Взаємодії в групах (опціонально) -- ============================================================================ CREATE TABLE IF NOT EXISTS group_interactions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), group_id UUID NOT NULL REFERENCES groups(group_id) ON DELETE CASCADE, platform_user_id VARCHAR(255) NOT NULL, -- Тема/контекст взаємодії topic VARCHAR(255), interaction_type VARCHAR(50), -- question, answer, discussion, feedback -- Часові мітки first_interaction_at TIMESTAMPTZ DEFAULT NOW(), last_interaction_at TIMESTAMPTZ DEFAULT NOW(), interaction_count INTEGER DEFAULT 1, -- Якість взаємодії sentiment_score FLOAT, -- -1.0 до 1.0 helpfulness_score FLOAT, -- 0.0 до 1.0 metadata JSONB DEFAULT '{}' ); CREATE INDEX idx_group_interactions_group ON group_interactions(group_id); CREATE INDEX idx_group_interactions_user ON group_interactions(platform_user_id); CREATE INDEX idx_group_interactions_topic ON group_interactions(topic); -- ============================================================================ -- 11. HELPER FUNCTIONS -- ============================================================================ -- Функція для автоматичного оновлення updated_at CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ language 'plpgsql'; -- Тригери для updated_at CREATE TRIGGER update_groups_updated_at BEFORE UPDATE ON groups FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_group_member_profiles_updated_at BEFORE UPDATE ON group_member_profiles FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_memory_users_updated_at BEFORE UPDATE ON memory_users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_memories_updated_at BEFORE UPDATE ON memories FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Функція для opt-out користувача з групи CREATE OR REPLACE FUNCTION memory_opt_out_group( p_group_id UUID, p_platform_user_id VARCHAR(255) ) RETURNS VOID AS $$ BEGIN -- Позначити учасника як no_memory UPDATE group_members SET no_memory_in_group = TRUE WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id; -- Деактивувати всі пам'яті цього користувача в групі UPDATE memories SET is_active = FALSE WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id; -- Видалити профіль DELETE FROM group_member_profiles WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id; -- Записати в аудит INSERT INTO memory_events (user_id, group_id, action, actor, actor_user_id, reason) VALUES (NULL, p_group_id, 'opt_out', 'user', p_platform_user_id, 'User requested opt-out from group memory'); END; $$ LANGUAGE plpgsql; -- Функція для повного видалення користувача з групи (forget) CREATE OR REPLACE FUNCTION memory_forget_in_group( p_group_id UUID, p_platform_user_id VARCHAR(255) ) RETURNS VOID AS $$ BEGIN -- Видалити всі пам'яті DELETE FROM memories WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id; -- Видалити профіль DELETE FROM group_member_profiles WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id; -- Очистити дані учасника (але залишити запис) UPDATE group_members SET no_memory_in_group = TRUE, nickname = NULL WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id; -- Записати в аудит INSERT INTO memory_events (group_id, action, actor, actor_user_id, reason) VALUES (p_group_id, 'deleted', 'user', p_platform_user_id, 'User requested full memory deletion in group'); END; $$ LANGUAGE plpgsql; -- ============================================================================ -- 12. VIEWS для зручності -- ============================================================================ -- View: Активні пам'яті користувача CREATE OR REPLACE VIEW v_active_user_memories AS SELECT m.*, u.display_name, g.name as group_name FROM memories m LEFT JOIN memory_users u ON m.user_id = u.user_id LEFT JOIN groups g ON m.group_id = g.group_id WHERE m.is_active = TRUE AND (m.expires_at IS NULL OR m.expires_at > NOW()); -- View: Учасники групи з профілями CREATE OR REPLACE VIEW v_group_members_with_profiles AS SELECT gm.*, gmp.role_hint, gmp.language_preference, gmp.topics_of_interest, gmp.notes_short, g.name as group_name, g.platform FROM group_members gm LEFT JOIN group_member_profiles gmp ON gm.group_id = gmp.group_id AND gm.platform_user_id = gmp.platform_user_id JOIN groups g ON gm.group_id = g.group_id WHERE gm.status = 'active'; -- ============================================================================ -- Result -- ============================================================================ SELECT 'Migration 046 completed: Memory Service full schema created' AS result;