-- Migration: Agent Fleet Infrastructure -- Adds tables for multi-instance OpenClaw orchestration -- Run after base schema exists -- ============================================ -- INSTANCE REGISTRY -- ============================================ CREATE TABLE IF NOT EXISTS instances ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL UNIQUE, role VARCHAR(50) NOT NULL CHECK (role IN ('gateway', 'department', 'task_worker', 'user_session')), parent_id UUID REFERENCES instances(id) ON DELETE SET NULL, status VARCHAR(20) NOT NULL DEFAULT 'starting' CHECK (status IN ('starting', 'running', 'paused', 'stopped', 'error')), config JSONB NOT NULL DEFAULT '{}', capabilities JSONB NOT NULL DEFAULT '[]', handles JSONB NOT NULL DEFAULT '[]', -- Projects/channels this instance handles max_children INT DEFAULT 5, max_workers INT DEFAULT 10, idle_timeout_minutes INT DEFAULT 30, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_heartbeat_at TIMESTAMPTZ, metadata JSONB DEFAULT '{}' ); CREATE INDEX idx_instances_role ON instances(role); CREATE INDEX idx_instances_parent ON instances(parent_id); CREATE INDEX idx_instances_status ON instances(status); -- ============================================ -- SESSIONS (User/Project Sessions) -- ============================================ CREATE TABLE IF NOT EXISTS sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), instance_id UUID NOT NULL REFERENCES instances(id) ON DELETE CASCADE, session_key VARCHAR(255) NOT NULL UNIQUE, user_id VARCHAR(255), -- Discord user ID, etc. channel_id VARCHAR(255), -- Discord channel ID project_context VARCHAR(255), -- Which project this session is for status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'idle', 'paused', 'closed')), context_window JSONB DEFAULT '[]', -- Conversation history metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), closed_at TIMESTAMPTZ ); CREATE INDEX idx_sessions_instance ON sessions(instance_id); CREATE INDEX idx_sessions_user ON sessions(user_id); CREATE INDEX idx_sessions_channel ON sessions(channel_id); CREATE INDEX idx_sessions_status ON sessions(status); -- ============================================ -- SESSION SUMMARIES (For RAG Context) -- ============================================ CREATE TABLE IF NOT EXISTS session_summaries ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_id UUID REFERENCES sessions(id) ON DELETE CASCADE, instance_id UUID NOT NULL REFERENCES instances(id) ON DELETE CASCADE, summary_type VARCHAR(50) NOT NULL CHECK (summary_type IN ('decision', 'key_info', 'action_item', 'lesson', 'milestone')), content TEXT NOT NULL, embedding VECTOR(1024), -- bge-m3 dimension metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_summaries_instance ON session_summaries(instance_id); CREATE INDEX idx_summaries_type ON session_summaries(summary_type); CREATE INDEX idx_summaries_session ON session_summaries(session_id); -- pgvector index for semantic search CREATE INDEX IF NOT EXISTS idx_summaries_embedding ON session_summaries USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); -- ============================================ -- EVENT LOG (Audit Trail) -- ============================================ CREATE TABLE IF NOT EXISTS event_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), instance_id UUID REFERENCES instances(id) ON DELETE SET NULL, session_id UUID REFERENCES sessions(id) ON DELETE SET NULL, event_type VARCHAR(100) NOT NULL, event_data JSONB DEFAULT '{}', severity VARCHAR(20) NOT NULL DEFAULT 'info' CHECK (severity IN ('debug', 'info', 'warn', 'error')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_event_log_instance ON event_log(instance_id); CREATE INDEX idx_event_log_session ON event_log(session_id); CREATE INDEX idx_event_log_type ON event_log(event_type); CREATE INDEX idx_event_log_severity ON event_log(severity); CREATE INDEX idx_event_log_created ON event_log(created_at); -- ============================================ -- CHANNEL MAPPINGS (Routing) -- ============================================ CREATE TABLE IF NOT EXISTS channel_mappings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel_id VARCHAR(255) NOT NULL UNIQUE, channel_type VARCHAR(50) NOT NULL, -- discord, telegram, etc. instance_id UUID NOT NULL REFERENCES instances(id) ON DELETE CASCADE, project_context VARCHAR(255), skills JSONB DEFAULT '[]', -- Allowed skills for this channel metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_channel_mappings_instance ON channel_mappings(instance_id); CREATE INDEX idx_channel_mappings_channel ON channel_mappings(channel_id); -- ============================================ -- TASK QUEUE (For Worker Spawning) -- ============================================ CREATE TABLE IF NOT EXISTS task_queue ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), task_type VARCHAR(50) NOT NULL, parent_instance_id UUID REFERENCES instances(id) ON DELETE SET NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'claimed', 'running', 'completed', 'failed', 'cancelled')), task_data JSONB NOT NULL DEFAULT '{}', result_data JSONB, assigned_worker_id UUID REFERENCES instances(id) ON DELETE SET NULL, priority INT DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ ); CREATE INDEX idx_task_queue_status ON task_queue(status); CREATE INDEX idx_task_queue_parent ON task_queue(parent_instance_id); CREATE INDEX idx_task_queue_priority ON task_queue(priority DESC, created_at); -- ============================================ -- DEFAULT INSTANCES (Seed Data) -- ============================================ INSERT INTO instances (name, role, status, config, capabilities, handles, max_children, max_workers) VALUES ('jarvis-main', 'gateway', 'running', '{"skills": ["orchestration", "mosaic-bootstrap", "all-projects-access"]}', '["spawn", "kill", "route", "delegate"]', '[]', 5, 10), ('jarvis-projects', 'department', 'running', '{"skills": ["coding-agent", "next-best-practices", "nestjs-best-practices", "fastapi"]}', '["spawn-workers", "code-review", "ci-cd"]', '["mosaic-stack", "sagephr", "lark-path"]', 3, 5), ('jarvis-research', 'department', 'running', '{"skills": ["web-search", "web-fetch", "brainstorming"]}', '["research", "discovery", "analysis"]', '[]', 2, 3), ('jarvis-operations', 'department', 'running', '{"skills": ["monitoring", "healthcheck", "maintenance"]}', '["monitoring", "maintenance", "alerts"]', '[]', 2, 3) ON CONFLICT (name) DO NOTHING;