95 lines
4.0 KiB
SQL
95 lines
4.0 KiB
SQL
-- A business task owns exactly one external Agent session. The external
|
|
-- session identifier and every conversational turn stay server-side and are
|
|
-- encrypted by the application before persistence.
|
|
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
organization_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
task_id uuid NOT NULL UNIQUE REFERENCES tasks(id) ON DELETE CASCADE,
|
|
conversation_id text,
|
|
provider text NOT NULL DEFAULT 'superagent',
|
|
external_session_id_ciphertext text,
|
|
status text NOT NULL DEFAULT 'pending'
|
|
CHECK (status IN ('pending', 'active', 'recovering', 'expired', 'failed', 'closed')),
|
|
current_turn integer NOT NULL DEFAULT 0 CHECK (current_turn >= 0),
|
|
recovery_count integer NOT NULL DEFAULT 0 CHECK (recovery_count >= 0),
|
|
last_error text,
|
|
last_message_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS agent_sessions_conversation_idx
|
|
ON agent_sessions (organization_id, conversation_id, updated_at DESC)
|
|
WHERE conversation_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS agent_sessions_pending_idx
|
|
ON agent_sessions (organization_id, conversation_id, status, updated_at DESC)
|
|
WHERE conversation_id IS NOT NULL AND status NOT IN ('closed', 'failed');
|
|
|
|
CREATE TABLE IF NOT EXISTS agent_session_messages (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
organization_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
agent_session_id uuid NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
|
|
task_id uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
turn_no integer NOT NULL CHECK (turn_no > 0),
|
|
role text NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
|
|
content_ciphertext text NOT NULL,
|
|
status text NOT NULL DEFAULT 'queued'
|
|
CHECK (status IN ('queued', 'sending', 'sent', 'replayed', 'failed')),
|
|
idempotency_key text,
|
|
error_code text,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
sent_at timestamptz,
|
|
UNIQUE (agent_session_id, turn_no, role)
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS agent_session_messages_idempotency_idx
|
|
ON agent_session_messages (organization_id, idempotency_key)
|
|
WHERE idempotency_key IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS agent_session_messages_queue_idx
|
|
ON agent_session_messages (agent_session_id, status, role, turn_no);
|
|
|
|
CREATE INDEX IF NOT EXISTS agent_session_messages_task_idx
|
|
ON agent_session_messages (organization_id, task_id, turn_no);
|
|
|
|
-- Existing tasks were created before task-scoped sessions existed. Backfill a
|
|
-- pending session and preserve the already encrypted original instruction as
|
|
-- the first user turn. The application will attach conversation metadata when
|
|
-- a later message arrives.
|
|
INSERT INTO agent_sessions (organization_id, task_id, status, current_turn, created_at, updated_at)
|
|
SELECT t.organization_id, t.id,
|
|
CASE WHEN t.status IN (
|
|
'completed', 'cancelled', 'parse_failed', 'parse_blocked', 'blocked',
|
|
'failed', 'dry_run', 'reconciliation_pending', 'execution_uncertain',
|
|
'saved_unverified', 'operation_blocked'
|
|
) THEN 'closed' ELSE 'pending' END,
|
|
1,
|
|
t.created_at,
|
|
t.updated_at
|
|
FROM tasks t
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM agent_sessions s WHERE s.task_id = t.id
|
|
);
|
|
|
|
INSERT INTO agent_session_messages
|
|
(organization_id, agent_session_id, task_id, turn_no, role, content_ciphertext, status, created_at, sent_at)
|
|
SELECT t.organization_id, s.id, t.id, 1, 'user', t.original_text_ciphertext,
|
|
CASE
|
|
WHEN t.status = 'parse_queued' THEN 'queued'
|
|
WHEN t.status = 'parse_running' THEN 'sending'
|
|
ELSE 'sent'
|
|
END,
|
|
t.created_at,
|
|
CASE WHEN t.status IN ('parse_queued', 'parse_running') THEN NULL ELSE t.created_at END
|
|
FROM tasks t
|
|
JOIN agent_sessions s ON s.task_id = t.id
|
|
WHERE t.original_text_ciphertext IS NOT NULL
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM agent_session_messages m
|
|
WHERE m.agent_session_id = s.id
|
|
AND m.turn_no = 1
|
|
AND m.role = 'user'
|
|
);
|