163 lines
5.9 KiB
Python
163 lines
5.9 KiB
Python
"""Initial schema — memory, directive, session, LLM config, rate limit tables
|
|
|
|
Revision ID: 001
|
|
Revises:
|
|
Create Date: 2026-04-12
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.dialects import postgresql
|
|
|
|
revision = '001'
|
|
down_revision = None
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# -----------------------------------------------------------------------
|
|
# Tier 1: Conversation memory per user
|
|
# Hard cap: 200 rows per user — enforced in application layer before insert
|
|
# -----------------------------------------------------------------------
|
|
op.execute("""
|
|
CREATE TABLE IF NOT EXISTS ab_conversation_memory (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INTEGER NOT NULL,
|
|
role VARCHAR(16) NOT NULL,
|
|
content TEXT NOT NULL,
|
|
directive_id VARCHAR(64),
|
|
is_summary BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
)
|
|
""")
|
|
op.execute("""
|
|
CREATE INDEX IF NOT EXISTS idx_conv_user
|
|
ON ab_conversation_memory(user_id, created_at DESC)
|
|
""")
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Tier 2: Operational memory — 90-day TTL
|
|
# -----------------------------------------------------------------------
|
|
op.execute("""
|
|
CREATE TABLE IF NOT EXISTS ab_operational_memory (
|
|
id SERIAL PRIMARY KEY,
|
|
scope VARCHAR(64) NOT NULL,
|
|
summary TEXT NOT NULL,
|
|
raw_data JSONB,
|
|
source_directive_id VARCHAR(64),
|
|
expires_at TIMESTAMP,
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
)
|
|
""")
|
|
op.execute("""
|
|
CREATE INDEX IF NOT EXISTS idx_op_scope
|
|
ON ab_operational_memory(scope, created_at DESC)
|
|
""")
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Tier 3: Long-term knowledge — permanent
|
|
# -----------------------------------------------------------------------
|
|
op.execute("""
|
|
CREATE TABLE IF NOT EXISTS ab_knowledge_store (
|
|
id SERIAL PRIMARY KEY,
|
|
entity_type VARCHAR(32) NOT NULL,
|
|
entity_key VARCHAR(128) NOT NULL,
|
|
facts JSONB NOT NULL,
|
|
updated_at TIMESTAMP DEFAULT NOW(),
|
|
UNIQUE(entity_type, entity_key)
|
|
)
|
|
""")
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Directive execution graph
|
|
# -----------------------------------------------------------------------
|
|
op.execute("""
|
|
CREATE TABLE IF NOT EXISTS ab_directive_log (
|
|
id SERIAL PRIMARY KEY,
|
|
directive_id VARCHAR(64) UNIQUE NOT NULL,
|
|
user_id INTEGER NOT NULL,
|
|
channel_id INTEGER NOT NULL,
|
|
raw_message TEXT NOT NULL,
|
|
intent_summary TEXT,
|
|
agents_involved JSONB DEFAULT '[]',
|
|
peer_calls JSONB DEFAULT '[]',
|
|
actions_taken JSONB DEFAULT '[]',
|
|
escalations JSONB DEFAULT '[]',
|
|
final_response TEXT,
|
|
status VARCHAR(32) DEFAULT 'pending',
|
|
started_at TIMESTAMP DEFAULT NOW(),
|
|
completed_at TIMESTAMP,
|
|
error TEXT
|
|
)
|
|
""")
|
|
op.execute("""
|
|
CREATE INDEX IF NOT EXISTS idx_dir_user
|
|
ON ab_directive_log(user_id, started_at DESC)
|
|
""")
|
|
op.execute("""
|
|
CREATE INDEX IF NOT EXISTS idx_dir_status
|
|
ON ab_directive_log(status)
|
|
""")
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Per-agent session state during execution
|
|
# -----------------------------------------------------------------------
|
|
op.execute("""
|
|
CREATE TABLE IF NOT EXISTS ab_agent_session (
|
|
id SERIAL PRIMARY KEY,
|
|
directive_id VARCHAR(64) NOT NULL,
|
|
agent_name VARCHAR(64) NOT NULL,
|
|
messages JSONB NOT NULL DEFAULT '[]',
|
|
status VARCHAR(32) DEFAULT 'running',
|
|
started_at TIMESTAMP DEFAULT NOW(),
|
|
completed_at TIMESTAMP
|
|
)
|
|
""")
|
|
|
|
# -----------------------------------------------------------------------
|
|
# LLM backend config — runtime per-agent model routing
|
|
# caller='__system__' stores the global privacy mode
|
|
# -----------------------------------------------------------------------
|
|
op.execute("""
|
|
CREATE TABLE IF NOT EXISTS ab_llm_config (
|
|
caller VARCHAR(64) PRIMARY KEY,
|
|
backend VARCHAR(16) NOT NULL,
|
|
set_by INTEGER,
|
|
set_at TIMESTAMP DEFAULT NOW(),
|
|
note TEXT
|
|
)
|
|
""")
|
|
|
|
# Seed default privacy mode = 'local'
|
|
op.execute("""
|
|
INSERT INTO ab_llm_config (caller, backend, note)
|
|
VALUES ('__system__', 'local', 'Default privacy mode — local Ollama only')
|
|
ON CONFLICT (caller) DO NOTHING
|
|
""")
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Rate limiting
|
|
# -----------------------------------------------------------------------
|
|
op.execute("""
|
|
CREATE TABLE IF NOT EXISTS ab_rate_limit (
|
|
user_id INTEGER NOT NULL,
|
|
window_start TIMESTAMP NOT NULL,
|
|
request_count INTEGER DEFAULT 0,
|
|
PRIMARY KEY (user_id, window_start)
|
|
)
|
|
""")
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.execute("DROP TABLE IF EXISTS ab_rate_limit")
|
|
op.execute("DROP TABLE IF EXISTS ab_llm_config")
|
|
op.execute("DROP TABLE IF EXISTS ab_agent_session")
|
|
op.execute("DROP INDEX IF EXISTS idx_dir_status")
|
|
op.execute("DROP INDEX IF EXISTS idx_dir_user")
|
|
op.execute("DROP TABLE IF EXISTS ab_directive_log")
|
|
op.execute("DROP TABLE IF EXISTS ab_knowledge_store")
|
|
op.execute("DROP INDEX IF EXISTS idx_op_scope")
|
|
op.execute("DROP TABLE IF EXISTS ab_operational_memory")
|
|
op.execute("DROP INDEX IF EXISTS idx_conv_user")
|
|
op.execute("DROP TABLE IF EXISTS ab_conversation_memory")
|