Files
goclaw/migrations/000001_init_schema.up.sql
T
2026-02-25 08:52:47 +07:00

546 lines
22 KiB
PL/PgSQL

-- GoClaw Multi-Tenant Schema
-- Requires: pgcrypto, pgvector extensions
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "vector";
-- UUID v7 function (matching backend-go)
CREATE OR REPLACE FUNCTION uuid_generate_v7() RETURNS uuid AS $$
DECLARE
unix_ts_ms bytea;
uuid_bytes bytea;
BEGIN
unix_ts_ms = substring(int8send(floor(extract(epoch from clock_timestamp()) * 1000)::bigint) from 3);
uuid_bytes = unix_ts_ms || gen_random_bytes(10);
uuid_bytes = set_byte(uuid_bytes, 6, (b'0111' || get_byte(uuid_bytes, 6)::bit(4))::bit(8)::int);
uuid_bytes = set_byte(uuid_bytes, 8, (b'10' || get_byte(uuid_bytes, 8)::bit(6))::bit(8)::int);
RETURN encode(uuid_bytes, 'hex')::uuid;
END
$$ LANGUAGE plpgsql VOLATILE;
-- ============================================================
-- 1. LLM Providers
-- ============================================================
CREATE TABLE llm_providers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
name VARCHAR(50) NOT NULL UNIQUE,
display_name VARCHAR(255),
provider_type VARCHAR(30) NOT NULL DEFAULT 'openai_compat',
api_base TEXT,
api_key TEXT,
enabled BOOLEAN NOT NULL DEFAULT true,
settings JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================================
-- 2. Agents & Access Control
-- ============================================================
CREATE TABLE agents (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
agent_key VARCHAR(100) NOT NULL UNIQUE,
display_name VARCHAR(255),
owner_id VARCHAR(255) NOT NULL,
provider VARCHAR(50) NOT NULL DEFAULT 'openrouter',
model VARCHAR(200) NOT NULL,
context_window INT NOT NULL DEFAULT 200000,
max_tool_iterations INT NOT NULL DEFAULT 20,
workspace TEXT NOT NULL DEFAULT '.',
restrict_to_workspace BOOLEAN NOT NULL DEFAULT true,
tools_config JSONB NOT NULL DEFAULT '{}',
sandbox_config JSONB,
subagents_config JSONB,
memory_config JSONB,
compaction_config JSONB,
context_pruning JSONB,
other_config JSONB NOT NULL DEFAULT '{}',
is_default BOOLEAN NOT NULL DEFAULT false,
agent_type VARCHAR(20) NOT NULL DEFAULT 'open',
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_agents_owner ON agents(owner_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_agents_status ON agents(status) WHERE deleted_at IS NULL;
CREATE TABLE agent_shares (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
user_id VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'user',
granted_by VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(agent_id, user_id)
);
CREATE INDEX idx_agent_shares_user ON agent_shares(user_id);
-- ============================================================
-- 3. Context Files & User Profiles
-- ============================================================
CREATE TABLE agent_context_files (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
file_name VARCHAR(255) NOT NULL,
content TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(agent_id, file_name)
);
CREATE TABLE user_context_files (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
user_id VARCHAR(255) NOT NULL,
file_name VARCHAR(255) NOT NULL,
content TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(agent_id, user_id, file_name)
);
CREATE TABLE user_agent_profiles (
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
user_id VARCHAR(255) NOT NULL,
workspace TEXT,
first_seen_at TIMESTAMPTZ DEFAULT NOW(),
last_seen_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (agent_id, user_id)
);
CREATE TABLE user_agent_overrides (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
user_id VARCHAR(255) NOT NULL,
provider VARCHAR(50),
model VARCHAR(200),
settings JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(agent_id, user_id)
);
-- ============================================================
-- 4. Sessions
-- ============================================================
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
session_key VARCHAR(500) NOT NULL UNIQUE,
agent_id UUID REFERENCES agents(id),
user_id VARCHAR(255),
messages JSONB NOT NULL DEFAULT '[]',
summary TEXT,
model VARCHAR(200),
provider VARCHAR(50),
channel VARCHAR(50),
input_tokens BIGINT NOT NULL DEFAULT 0,
output_tokens BIGINT NOT NULL DEFAULT 0,
compaction_count INT NOT NULL DEFAULT 0,
memory_flush_compaction_count INT NOT NULL DEFAULT 0,
memory_flush_at BIGINT DEFAULT 0,
label VARCHAR(500),
spawned_by VARCHAR(200),
spawn_depth INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_sessions_agent ON sessions(agent_id);
CREATE INDEX idx_sessions_user ON sessions(user_id);
CREATE INDEX idx_sessions_updated ON sessions(updated_at DESC);
-- ============================================================
-- 5. Memory (pgvector + tsvector)
-- ============================================================
CREATE TABLE memory_documents (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
user_id VARCHAR(255),
path VARCHAR(500) NOT NULL,
content TEXT NOT NULL DEFAULT '',
hash VARCHAR(64) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE UNIQUE INDEX idx_memdoc_unique ON memory_documents(agent_id, COALESCE(user_id, ''), path);
CREATE INDEX idx_memdoc_agent_user ON memory_documents(agent_id, user_id);
CREATE TABLE memory_chunks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
document_id UUID REFERENCES memory_documents(id) ON DELETE CASCADE,
user_id VARCHAR(255),
path TEXT NOT NULL,
start_line INT NOT NULL DEFAULT 0,
end_line INT NOT NULL DEFAULT 0,
hash VARCHAR(64) NOT NULL,
text TEXT NOT NULL,
embedding vector(1536),
tsv tsvector GENERATED ALWAYS AS (to_tsvector('simple', text)) STORED,
-- NOTE: 'simple' config (no stemming) works correctly for Vietnamese and other non-English languages
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_mem_agent_user ON memory_chunks(agent_id, user_id);
CREATE INDEX idx_mem_global ON memory_chunks(agent_id) WHERE user_id IS NULL;
CREATE INDEX idx_mem_document ON memory_chunks(document_id);
CREATE INDEX idx_mem_tsv ON memory_chunks USING GIN(tsv);
CREATE INDEX idx_mem_vec ON memory_chunks USING hnsw(embedding vector_cosine_ops);
CREATE TABLE embedding_cache (
hash VARCHAR(64) NOT NULL,
provider VARCHAR(50) NOT NULL,
model VARCHAR(200) NOT NULL,
embedding vector(1536),
dims INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (hash, provider, model)
);
-- ============================================================
-- 6. Skills (metadata + filesystem content)
-- ============================================================
CREATE TABLE skills (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
owner_id VARCHAR(255) NOT NULL,
visibility VARCHAR(10) NOT NULL DEFAULT 'private',
version INT NOT NULL DEFAULT 1,
status VARCHAR(20) NOT NULL DEFAULT 'active',
frontmatter JSONB NOT NULL DEFAULT '{}',
file_path TEXT NOT NULL,
file_size BIGINT NOT NULL DEFAULT 0,
file_hash VARCHAR(64),
embedding vector(1536),
tags TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_skills_owner ON skills(owner_id);
CREATE INDEX idx_skills_visibility ON skills(visibility) WHERE status = 'active';
CREATE INDEX idx_skills_slug ON skills(slug);
CREATE INDEX idx_skills_embedding ON skills USING hnsw(embedding vector_cosine_ops);
CREATE INDEX idx_skills_tags ON skills USING GIN(tags);
CREATE TABLE skill_agent_grants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
skill_id UUID NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
pinned_version INT NOT NULL,
granted_by VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(skill_id, agent_id)
);
CREATE INDEX idx_skill_agent_grants_agent ON skill_agent_grants(agent_id);
CREATE TABLE skill_user_grants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
skill_id UUID NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
user_id VARCHAR(255) NOT NULL,
granted_by VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(skill_id, user_id)
);
CREATE INDEX idx_skill_user_grants_user ON skill_user_grants(user_id);
-- ============================================================
-- 7. Cron Jobs
-- ============================================================
CREATE TABLE cron_jobs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
agent_id UUID REFERENCES agents(id),
user_id TEXT,
name VARCHAR(255) NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT true,
schedule_kind VARCHAR(10) NOT NULL,
cron_expression VARCHAR(100),
interval_ms BIGINT,
run_at TIMESTAMPTZ,
timezone VARCHAR(50),
payload JSONB NOT NULL,
delete_after_run BOOLEAN NOT NULL DEFAULT false,
next_run_at TIMESTAMPTZ,
last_run_at TIMESTAMPTZ,
last_status VARCHAR(20),
last_error TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_cron_jobs_user_id ON cron_jobs(user_id);
CREATE INDEX idx_cron_jobs_agent_user ON cron_jobs(agent_id, user_id);
CREATE TABLE cron_run_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
job_id UUID NOT NULL REFERENCES cron_jobs(id) ON DELETE CASCADE,
agent_id UUID REFERENCES agents(id),
status VARCHAR(20) NOT NULL,
summary TEXT,
error TEXT,
duration_ms INT,
input_tokens INT DEFAULT 0,
output_tokens INT DEFAULT 0,
ran_at TIMESTAMPTZ DEFAULT NOW(),
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_cron_run_logs_job ON cron_run_logs(job_id, ran_at DESC);
-- ============================================================
-- 8. Pairing
-- ============================================================
CREATE TABLE pairing_requests (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
code VARCHAR(8) NOT NULL UNIQUE,
sender_id VARCHAR(200) NOT NULL,
channel VARCHAR(255) NOT NULL,
chat_id VARCHAR(200) NOT NULL,
account_id VARCHAR(100) NOT NULL DEFAULT 'default',
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE paired_devices (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
sender_id VARCHAR(200) NOT NULL,
channel VARCHAR(255) NOT NULL,
chat_id VARCHAR(200) NOT NULL,
paired_by VARCHAR(100) NOT NULL DEFAULT 'operator',
paired_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(sender_id, channel)
);
-- ============================================================
-- 9. LLM Tracing
-- ============================================================
CREATE TABLE traces (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
agent_id UUID,
user_id VARCHAR(255),
session_key TEXT,
run_id TEXT,
start_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
end_time TIMESTAMPTZ,
duration_ms INT,
name TEXT,
channel VARCHAR(50),
input_preview TEXT,
output_preview TEXT,
total_input_tokens INT DEFAULT 0,
total_output_tokens INT DEFAULT 0,
total_cost NUMERIC(12,6) DEFAULT 0,
span_count INT DEFAULT 0,
llm_call_count INT DEFAULT 0,
tool_call_count INT DEFAULT 0,
status VARCHAR(20) DEFAULT 'running',
error TEXT,
metadata JSONB,
tags TEXT[],
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_traces_agent_time ON traces(agent_id, created_at DESC);
CREATE INDEX idx_traces_user_time ON traces(user_id, created_at DESC) WHERE user_id IS NOT NULL;
CREATE INDEX idx_traces_session ON traces(session_key, created_at DESC) WHERE session_key IS NOT NULL;
CREATE INDEX idx_traces_status ON traces(status) WHERE status = 'error';
CREATE TABLE spans (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
trace_id UUID NOT NULL,
parent_span_id UUID,
agent_id UUID,
span_type VARCHAR(20) NOT NULL,
name TEXT,
start_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
end_time TIMESTAMPTZ,
duration_ms INT,
status VARCHAR(20) DEFAULT 'running',
error TEXT,
level VARCHAR(10) DEFAULT 'DEFAULT',
model VARCHAR(200),
provider VARCHAR(50),
input_tokens INT,
output_tokens INT,
total_cost NUMERIC(12,8),
finish_reason VARCHAR(50),
model_params JSONB,
tool_name VARCHAR(200),
tool_call_id VARCHAR(100),
input_preview TEXT,
output_preview TEXT,
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_spans_trace ON spans(trace_id, start_time);
CREATE INDEX idx_spans_parent ON spans(parent_span_id) WHERE parent_span_id IS NOT NULL;
CREATE INDEX idx_spans_agent_time ON spans(agent_id, created_at DESC);
CREATE INDEX idx_spans_type ON spans(span_type, created_at DESC);
CREATE INDEX idx_spans_model ON spans(model, created_at DESC) WHERE model IS NOT NULL;
CREATE INDEX idx_spans_error ON spans(status) WHERE status = 'error';
-- ============================================================
-- 10. MCP Servers (External Tool Providers)
-- ============================================================
CREATE TABLE mcp_servers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
name VARCHAR(255) NOT NULL UNIQUE,
display_name VARCHAR(255),
transport VARCHAR(50) NOT NULL, -- "stdio", "sse", "streamable-http"
command TEXT, -- stdio: command to spawn
args JSONB DEFAULT '[]', -- stdio: command arguments
url TEXT, -- sse/http: server URL
headers JSONB DEFAULT '{}', -- sse/http: HTTP headers
env JSONB DEFAULT '{}', -- stdio: environment variables
api_key TEXT, -- encrypted (AES-256-GCM)
tool_prefix VARCHAR(50), -- optional prefix for tool names
timeout_sec INT DEFAULT 60,
settings JSONB NOT NULL DEFAULT '{}',
enabled BOOLEAN NOT NULL DEFAULT true,
created_by VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE mcp_agent_grants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
server_id UUID NOT NULL REFERENCES mcp_servers(id) ON DELETE CASCADE,
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
enabled BOOLEAN NOT NULL DEFAULT true,
tool_allow JSONB, -- ["tool1", "tool2"] (null = all)
tool_deny JSONB, -- ["dangerous_tool"]
config_overrides JSONB,
granted_by VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(server_id, agent_id)
);
CREATE INDEX idx_mcp_agent_grants_agent ON mcp_agent_grants(agent_id);
CREATE TABLE mcp_user_grants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
server_id UUID NOT NULL REFERENCES mcp_servers(id) ON DELETE CASCADE,
user_id VARCHAR(255) NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT true,
tool_allow JSONB,
tool_deny JSONB,
granted_by VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(server_id, user_id)
);
CREATE INDEX idx_mcp_user_grants_user ON mcp_user_grants(user_id);
CREATE TABLE mcp_access_requests (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
server_id UUID NOT NULL REFERENCES mcp_servers(id) ON DELETE CASCADE,
agent_id UUID REFERENCES agents(id) ON DELETE CASCADE,
user_id VARCHAR(255),
scope VARCHAR(10) NOT NULL, -- "agent" or "user"
status VARCHAR(20) NOT NULL DEFAULT 'pending', -- "pending", "approved", "rejected"
reason TEXT,
tool_allow JSONB, -- requested tool subset (null = all)
requested_by VARCHAR(255) NOT NULL,
reviewed_by VARCHAR(255),
reviewed_at TIMESTAMPTZ,
review_note TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_mcp_requests_status ON mcp_access_requests(status) WHERE status = 'pending';
CREATE INDEX idx_mcp_requests_server ON mcp_access_requests(server_id);
-- ============================================================
-- 11. Custom Tools (Dynamic Tools from DB)
-- ============================================================
CREATE TABLE custom_tools (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
name VARCHAR(100) NOT NULL,
description TEXT NOT NULL DEFAULT '',
parameters JSONB NOT NULL DEFAULT '{}',
command TEXT NOT NULL,
working_dir TEXT DEFAULT '',
timeout_seconds INT DEFAULT 60,
env BYTEA, -- encrypted env vars (AES-256-GCM)
agent_id UUID REFERENCES agents(id) ON DELETE CASCADE,
enabled BOOLEAN DEFAULT TRUE,
created_by VARCHAR(255) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Global tools: unique name when agent_id IS NULL
CREATE UNIQUE INDEX idx_custom_tools_name_global ON custom_tools(name) WHERE agent_id IS NULL;
-- Per-agent tools: unique (name, agent_id) when agent_id IS NOT NULL
CREATE UNIQUE INDEX idx_custom_tools_name_agent ON custom_tools(name, agent_id) WHERE agent_id IS NOT NULL;
-- Fast lookup by agent
CREATE INDEX idx_custom_tools_agent ON custom_tools(agent_id) WHERE agent_id IS NOT NULL;
-- ============================================================
-- 12. Channel Instances
-- ============================================================
CREATE TABLE channel_instances (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
name VARCHAR(100) NOT NULL UNIQUE,
display_name VARCHAR(255) DEFAULT '',
channel_type VARCHAR(50) NOT NULL,
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
credentials BYTEA,
config JSONB DEFAULT '{}',
enabled BOOLEAN DEFAULT true,
created_by VARCHAR(255) DEFAULT '',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_channel_instances_type ON channel_instances(channel_type);
CREATE INDEX idx_channel_instances_agent ON channel_instances(agent_id);
-- ============================================================
-- 13. Config Secrets
-- ============================================================
CREATE TABLE config_secrets (
key VARCHAR(100) PRIMARY KEY,
value BYTEA NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- ============================================================
-- 14. Group File Writers (Telegram group write permissions)
-- ============================================================
CREATE TABLE group_file_writers (
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
group_id VARCHAR(255) NOT NULL,
user_id VARCHAR(255) NOT NULL,
display_name VARCHAR(255),
username VARCHAR(255),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (agent_id, group_id, user_id)
);