Files
temp/alembic/versions/a7_001_session_persistence.py

127 lines
4.1 KiB
Python

"""Add sessions and session_messages tables.
This migration creates the two session persistence tables needed for
the Session service:
- ``sessions``: Persistent conversation threads tied to orchestrator actors.
- ``session_messages``: Ordered messages within a session conversation.
Revision ID: a7_001_session_persistence
Revises: c1_001_tool_registry
Create Date: 2026-02-14 12:00:00
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "a7_001_session_persistence"
down_revision: str | Sequence[str] | None = "c1_001_tool_registry"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
"""Create sessions and session_messages tables."""
# --- sessions table ---
op.create_table(
"sessions",
# PK: ULID (26-char string)
sa.Column("session_id", sa.String(26), nullable=False),
# Optional actor binding
sa.Column("actor_name", sa.String(255), nullable=True),
# Namespace for session ownership
sa.Column(
"namespace",
sa.String(100),
nullable=False,
server_default="local",
),
# Session-level automation override
sa.Column("automation_level", sa.String(30), nullable=True),
# JSON: list of linked plan ULIDs
sa.Column("linked_plan_ids_json", sa.Text(), nullable=True),
# JSON: token usage tracking
sa.Column("token_usage_json", sa.Text(), nullable=True),
# JSON: arbitrary session metadata
sa.Column("metadata_json", sa.Text(), nullable=True),
# Timestamps (ISO-8601 strings)
sa.Column("created_at", sa.String(30), nullable=False),
sa.Column("updated_at", sa.String(30), nullable=False),
# Constraints
sa.PrimaryKeyConstraint("session_id"),
)
op.create_index(
"ix_sessions_created_at",
"sessions",
["created_at"],
unique=False,
)
op.create_index(
"ix_sessions_actor_name",
"sessions",
["actor_name"],
unique=False,
)
# --- session_messages table ---
op.create_table(
"session_messages",
# PK: ULID (26-char string)
sa.Column("message_id", sa.String(26), nullable=False),
# FK to sessions
sa.Column(
"session_id",
sa.String(26),
sa.ForeignKey(
"sessions.session_id",
ondelete="CASCADE",
name="fk_session_messages_session",
),
nullable=False,
),
# Message role: user | assistant | system | tool
sa.Column("role", sa.String(20), nullable=False),
# Message content (full text)
sa.Column("content", sa.Text(), nullable=False),
# Ordering index within the session
sa.Column("sequence", sa.Integer(), nullable=False),
# Message creation timestamp (ISO-8601 string)
sa.Column("timestamp", sa.String(30), nullable=False),
# JSON: optional message metadata
sa.Column("metadata_json", sa.Text(), nullable=True),
# Tool call identifier (required when role is 'tool')
sa.Column("tool_call_id", sa.String(255), nullable=True),
# Constraints
sa.PrimaryKeyConstraint("message_id"),
)
op.create_index(
"ix_session_messages_session_id",
"session_messages",
["session_id"],
unique=False,
)
op.create_index(
"ix_session_messages_sequence",
"session_messages",
["session_id", "sequence"],
unique=False,
)
def downgrade() -> None:
"""Drop sessions and session_messages tables."""
# Drop in reverse FK order: messages -> sessions
op.drop_index("ix_session_messages_sequence", table_name="session_messages")
op.drop_index("ix_session_messages_session_id", table_name="session_messages")
op.drop_table("session_messages")
op.drop_index("ix_sessions_actor_name", table_name="sessions")
op.drop_index("ix_sessions_created_at", table_name="sessions")
op.drop_table("sessions")