SET NAMES utf8mb4; -- One row per managed test account. model_id 0 falls back to the configured -- default model so operations can switch every agent at once. CREATE TABLE IF NOT EXISTS ai_agents ( user_id BIGINT UNSIGNED NOT NULL, model_id BIGINT UNSIGNED NOT NULL DEFAULT 0, persona VARCHAR(1000) NOT NULL DEFAULT '', enabled TINYINT(1) NOT NULL DEFAULT 1, daily_reply_limit INT UNSIGNED NOT NULL DEFAULT 0, reply_count INT UNSIGNED NOT NULL DEFAULT 0, last_reply_at DATETIME(3) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), PRIMARY KEY (user_id), KEY idx_ai_agents_enabled (enabled) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- A durable queue rather than an in-memory one: replies survive a restart and -- several instances can share it. Claiming is an UPDATE that stamps claim_token -- instead of SELECT ... FOR UPDATE SKIP LOCKED, because deployments still run -- MySQL 5.7 where SKIP LOCKED does not parse. -- pending_key is NULL once a job leaves the pending state, so the unique index -- allows exactly one waiting job per conversation and collapses a burst of -- incoming messages into a single reply. CREATE TABLE IF NOT EXISTS ai_reply_jobs ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, conversation_id BIGINT UNSIGNED NOT NULL, agent_user_id BIGINT UNSIGNED NOT NULL, trigger_message_id BIGINT UNSIGNED NOT NULL DEFAULT 0, status TINYINT UNSIGNED NOT NULL DEFAULT 0, attempts TINYINT UNSIGNED NOT NULL DEFAULT 0, run_after DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), claim_token CHAR(32) NOT NULL DEFAULT '', last_error VARCHAR(500) NOT NULL DEFAULT '', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), pending_key BIGINT UNSIGNED GENERATED ALWAYS AS (IF(status = 0, conversation_id, NULL)) STORED, PRIMARY KEY (id), UNIQUE KEY uk_ai_reply_jobs_pending (pending_key), KEY idx_ai_reply_jobs_due (status, run_after), KEY idx_ai_reply_jobs_claim (claim_token), KEY idx_ai_reply_jobs_agent (agent_user_id, created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;