90 lines
3.4 KiB
PL/PgSQL
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';
|