This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/database/migrations/008_memory.sql
2026-03-11 11:22:38 +00:00

90 lines
3.4 KiB
PL/PgSQL

-- ==========================================
-- Chat Switchboard — 008 Memory
-- ==========================================
-- Long-term memory across conversations, extraction tracking.
-- Consolidated v0.28.0: adds last_compacted_at + decay_rate.
-- ==========================================
-- =========================================
-- MEMORIES
-- =========================================
CREATE TABLE IF NOT EXISTS memories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
scope TEXT NOT NULL CHECK (scope IN ('user', 'persona', 'persona_user')),
owner_id UUID NOT NULL,
user_id UUID,
key TEXT NOT NULL,
value TEXT NOT NULL,
source_channel_id UUID,
confidence REAL NOT NULL DEFAULT 1.0,
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'pending_review', 'archived')),
embedding vector(3072),
-- Compaction support (v0.28.0)
last_compacted_at TIMESTAMPTZ,
decay_rate REAL NOT NULL DEFAULT 0.0,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_memories_scope_owner
ON memories(scope, owner_id, status)
WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_memories_persona_user
ON memories(owner_id, user_id)
WHERE scope = 'persona_user' AND status = 'active';
CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_unique_key
ON memories(scope, owner_id, COALESCE(user_id, '00000000-0000-0000-0000-000000000000'), key);
CREATE INDEX IF NOT EXISTS idx_memories_fts
ON memories USING gin(to_tsvector('english', key || ' ' || value));
CREATE INDEX IF NOT EXISTS idx_memories_source_channel
ON memories(source_channel_id)
WHERE source_channel_id IS NOT NULL;
COMMENT ON TABLE memories IS 'Long-term memory facts persisted across conversations';
COMMENT ON COLUMN memories.scope IS 'user = personal facts; persona = shared; persona_user = per-user within a persona';
COMMENT ON COLUMN memories.owner_id IS 'user_id for user scope, persona_id for persona/persona_user scopes';
COMMENT ON COLUMN memories.confidence IS '0.0-1.0 confidence score — higher = more certain';
COMMENT ON COLUMN memories.last_compacted_at IS 'Last time this memory was evaluated by the compaction process';
COMMENT ON COLUMN memories.decay_rate IS 'Rate at which confidence decreases over time (0.0 = no decay)';
CREATE OR REPLACE FUNCTION update_memories_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_memories_updated_at ON memories;
CREATE TRIGGER trg_memories_updated_at
BEFORE UPDATE ON memories
FOR EACH ROW EXECUTE FUNCTION update_memories_updated_at();
-- =========================================
-- MEMORY EXTRACTION LOG
-- =========================================
CREATE TABLE IF NOT EXISTS memory_extraction_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL,
user_id UUID NOT NULL,
last_message_id UUID NOT NULL,
extracted_at TIMESTAMPTZ DEFAULT now(),
memory_count INT NOT NULL DEFAULT 0,
UNIQUE(channel_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_mem_extract_log_channel
ON memory_extraction_log(channel_id);
COMMENT ON TABLE memory_extraction_log IS 'Tracks extraction progress per channel+user to prevent re-processing';