Files
letta-server/alembic/versions/2c059cad97cc_create_sqlite_baseline_schema.py
Kian Jones b8e9a80d93 merge this (#4759)
* wait I forgot to comit locally

* cp the entire core directory and then rm the .git subdir
2025-09-17 15:47:40 -07:00

799 lines
44 KiB
Python

"""create_sqlite_baseline_schema
Revision ID: 2c059cad97cc
Revises: 495f3f474131
Create Date: 2025-07-16 14:34:21.280233
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from letta.settings import settings
# revision identifiers, used by Alembic.
revision: str = "2c059cad97cc"
down_revision: Union[str, None] = "495f3f474131"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Only run this migration for SQLite
if settings.letta_pg_uri_no_default:
return
# Create the exact schema that matches the current PostgreSQL state
# This is a snapshot of the schema at the time of this migration
# Based on the schema provided by Andy
# Organizations table
op.create_table(
"organizations",
sa.Column("id", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("privileged_tools", sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
# Agents table
op.create_table(
"agents",
sa.Column("id", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.Column("message_ids", sa.JSON(), nullable=True),
sa.Column("system", sa.String(), nullable=True),
sa.Column("agent_type", sa.String(), nullable=True),
sa.Column("llm_config", sa.JSON(), nullable=True),
sa.Column("embedding_config", sa.JSON(), nullable=True),
sa.Column("metadata_", sa.JSON(), nullable=True),
sa.Column("tool_rules", sa.JSON(), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("project_id", sa.String(), nullable=True),
sa.Column("template_id", sa.String(), nullable=True),
sa.Column("base_template_id", sa.String(), nullable=True),
sa.Column("message_buffer_autoclear", sa.Boolean(), nullable=False),
sa.Column("enable_sleeptime", sa.Boolean(), nullable=True),
sa.Column("response_format", sa.JSON(), nullable=True),
sa.Column("last_run_completion", sa.DateTime(timezone=True), nullable=True),
sa.Column("last_run_duration_ms", sa.Integer(), nullable=True),
sa.Column("timezone", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
)
op.create_index("ix_agents_created_at", "agents", ["created_at", "id"])
# Block history table (created before block table so block can reference it)
op.create_table(
"block_history",
sa.Column("id", sa.String(), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("label", sa.String(), nullable=False),
sa.Column("value", sa.Text(), nullable=False),
sa.Column("limit", sa.BigInteger(), nullable=False),
sa.Column("metadata_", sa.JSON(), nullable=True),
sa.Column("actor_type", sa.String(), nullable=True),
sa.Column("actor_id", sa.String(), nullable=True),
sa.Column("block_id", sa.String(), nullable=False),
sa.Column("sequence_number", sa.Integer(), nullable=False),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
# Note: block_id foreign key will be added later since block table doesn't exist yet
)
op.create_index("ix_block_history_block_id_sequence", "block_history", ["block_id", "sequence_number"], unique=True)
# Block table
op.create_table(
"block",
sa.Column("id", sa.String(), nullable=False),
sa.Column("value", sa.String(), nullable=False),
sa.Column("limit", sa.Integer(), nullable=False),
sa.Column("template_name", sa.String(), nullable=True),
sa.Column("label", sa.String(), nullable=False),
sa.Column("metadata_", sa.JSON(), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.Column("is_template", sa.Boolean(), nullable=False),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("current_history_entry_id", sa.String(), nullable=True),
sa.Column("version", sa.Integer(), server_default="1", nullable=False),
sa.Column("read_only", sa.Boolean(), nullable=False),
sa.Column("preserve_on_migration", sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.ForeignKeyConstraint(["current_history_entry_id"], ["block_history.id"], name="fk_block_current_history_entry"),
sa.UniqueConstraint("id", "label", name="unique_block_id_label"),
)
op.create_index("created_at_label_idx", "block", ["created_at", "label"])
op.create_index("ix_block_current_history_entry_id", "block", ["current_history_entry_id"])
# Note: Foreign key constraint for block_history.block_id cannot be added in SQLite after table creation
# This will be enforced at the ORM level
# Sources table
op.create_table(
"sources",
sa.Column("id", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("embedding_config", sa.JSON(), nullable=False),
sa.Column("description", sa.String(), nullable=True),
sa.Column("metadata_", sa.JSON(), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("instructions", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.UniqueConstraint("name", "organization_id", name="uq_source_name_organization"),
)
op.create_index("source_created_at_id_idx", "sources", ["created_at", "id"])
# Files table
op.create_table(
"files",
sa.Column("id", sa.String(), nullable=False),
sa.Column("source_id", sa.String(), nullable=False),
sa.Column("file_name", sa.String(), nullable=True),
sa.Column("file_path", sa.String(), nullable=True),
sa.Column("file_type", sa.String(), nullable=True),
sa.Column("file_size", sa.Integer(), nullable=True),
sa.Column("file_creation_date", sa.String(), nullable=True),
sa.Column("file_last_modified_date", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("processing_status", sa.String(), nullable=False),
sa.Column("error_message", sa.Text(), nullable=True),
sa.Column("original_file_name", sa.String(), nullable=True),
sa.Column("total_chunks", sa.Integer(), nullable=True),
sa.Column("chunks_embedded", sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["source_id"], ["sources.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
)
# Note: SQLite doesn't support expression indexes, so these are simplified
op.create_index("ix_files_org_created", "files", ["organization_id"])
op.create_index("ix_files_processing_status", "files", ["processing_status"])
op.create_index("ix_files_source_created", "files", ["source_id"])
# Users table
op.create_table(
"users",
sa.Column("id", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
)
# Jobs table
op.create_table(
"jobs",
sa.Column("id", sa.String(), nullable=False),
sa.Column("user_id", sa.String(), nullable=False),
sa.Column("status", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("metadata_", sa.JSON(), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("job_type", sa.String(), nullable=False),
sa.Column("request_config", sa.JSON(), nullable=True),
sa.Column("callback_url", sa.String(), nullable=True),
sa.Column("callback_sent_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("callback_status_code", sa.Integer(), nullable=True),
sa.Column("callback_error", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["user_id"], ["users.id"]),
)
op.create_index("ix_jobs_created_at", "jobs", ["created_at", "id"])
# Tools table
op.create_table(
"tools",
sa.Column("id", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("description", sa.String(), nullable=True),
sa.Column("source_type", sa.String(), nullable=False),
sa.Column("source_code", sa.String(), nullable=True),
sa.Column("json_schema", sa.JSON(), nullable=True),
sa.Column("tags", sa.JSON(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("return_char_limit", sa.Integer(), nullable=True),
sa.Column("tool_type", sa.String(), nullable=False),
sa.Column("args_json_schema", sa.JSON(), nullable=True),
sa.Column("metadata_", sa.JSON(), nullable=True),
sa.Column("pip_requirements", sa.JSON(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.UniqueConstraint("name", "organization_id", name="uix_name_organization"),
)
op.create_index("ix_tools_created_at_name", "tools", ["created_at", "name"])
# Additional tables based on Andy's schema
# Agents tags table
op.create_table(
"agents_tags",
sa.Column("agent_id", sa.String(), nullable=False),
sa.Column("tag", sa.String(), nullable=False),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"]),
sa.UniqueConstraint("agent_id", "tag", name="unique_agent_tag"),
)
op.create_index("ix_agents_tags_agent_id_tag", "agents_tags", ["agent_id", "tag"])
# Sandbox configs table
op.create_table(
"sandbox_configs",
sa.Column("id", sa.String(), nullable=False),
sa.Column("type", sa.String(), nullable=False), # sandboxtype in PG
sa.Column("config", sa.JSON(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.UniqueConstraint("type", "organization_id", name="uix_type_organization"),
)
# Sandbox environment variables table
op.create_table(
"sandbox_environment_variables",
sa.Column("id", sa.String(), nullable=False),
sa.Column("key", sa.String(), nullable=False),
sa.Column("value", sa.String(), nullable=False),
sa.Column("description", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("sandbox_config_id", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.ForeignKeyConstraint(["sandbox_config_id"], ["sandbox_configs.id"]),
sa.UniqueConstraint("key", "sandbox_config_id", name="uix_key_sandbox_config"),
)
# Blocks agents table
op.create_table(
"blocks_agents",
sa.Column("agent_id", sa.String(), nullable=False),
sa.Column("block_id", sa.String(), nullable=False),
sa.Column("block_label", sa.String(), nullable=False),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"]),
sa.ForeignKeyConstraint(["block_id", "block_label"], ["block.id", "block.label"], deferrable=True, initially="DEFERRED"),
sa.UniqueConstraint("agent_id", "block_label", name="unique_label_per_agent"),
sa.UniqueConstraint("agent_id", "block_id", name="unique_agent_block"),
)
op.create_index("ix_blocks_agents_block_label_agent_id", "blocks_agents", ["block_label", "agent_id"])
op.create_index("ix_blocks_block_label", "blocks_agents", ["block_label"])
# Tools agents table
op.create_table(
"tools_agents",
sa.Column("agent_id", sa.String(), nullable=False),
sa.Column("tool_id", sa.String(), nullable=False),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["tool_id"], ["tools.id"], ondelete="CASCADE"),
sa.UniqueConstraint("agent_id", "tool_id", name="unique_agent_tool"),
)
# Sources agents table
op.create_table(
"sources_agents",
sa.Column("agent_id", sa.String(), nullable=False),
sa.Column("source_id", sa.String(), nullable=False),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["source_id"], ["sources.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("agent_id", "source_id"),
)
# Agent passages table (using BLOB for vectors in SQLite)
op.create_table(
"agent_passages",
sa.Column("id", sa.String(), nullable=False),
sa.Column("text", sa.String(), nullable=False),
sa.Column("embedding_config", sa.JSON(), nullable=False),
sa.Column("metadata_", sa.JSON(), nullable=False),
sa.Column("embedding", sa.BLOB(), nullable=True), # CommonVector becomes BLOB in SQLite
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("agent_id", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"], ondelete="CASCADE"),
)
# Note: agent_passages_org_idx is not created for SQLite as it's expected to be different
op.create_index("agent_passages_created_at_id_idx", "agent_passages", ["created_at", "id"])
op.create_index("ix_agent_passages_org_agent", "agent_passages", ["organization_id", "agent_id"])
# Source passages table (using BLOB for vectors in SQLite)
op.create_table(
"source_passages",
sa.Column("id", sa.String(), nullable=False),
sa.Column("text", sa.String(), nullable=False),
sa.Column("embedding_config", sa.JSON(), nullable=False),
sa.Column("metadata_", sa.JSON(), nullable=False),
sa.Column("embedding", sa.BLOB(), nullable=True), # CommonVector becomes BLOB in SQLite
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("file_id", sa.String(), nullable=True),
sa.Column("source_id", sa.String(), nullable=False),
sa.Column("file_name", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.ForeignKeyConstraint(["file_id"], ["files.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["source_id"], ["sources.id"], ondelete="CASCADE"),
)
# Note: source_passages_org_idx is not created for SQLite as it's expected to be different
op.create_index("source_passages_created_at_id_idx", "source_passages", ["created_at", "id"])
# Message sequence is handled by the sequence_id field in messages table
# Messages table
op.create_table(
"messages",
sa.Column("id", sa.String(), nullable=False),
sa.Column("agent_id", sa.String(), nullable=False),
sa.Column("role", sa.String(), nullable=False),
sa.Column("text", sa.String(), nullable=True),
sa.Column("model", sa.String(), nullable=True),
sa.Column("name", sa.String(), nullable=True),
sa.Column("tool_calls", sa.JSON(), nullable=False),
sa.Column("tool_call_id", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("step_id", sa.String(), nullable=True),
sa.Column("otid", sa.String(), nullable=True),
sa.Column("tool_returns", sa.JSON(), nullable=True),
sa.Column("group_id", sa.String(), nullable=True),
sa.Column("content", sa.JSON(), nullable=True),
sa.Column("sequence_id", sa.BigInteger(), nullable=False),
sa.Column("sender_id", sa.String(), nullable=True),
sa.Column("batch_item_id", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["step_id"], ["steps.id"], ondelete="SET NULL"),
sa.UniqueConstraint("sequence_id", name="uq_messages_sequence_id"),
)
op.create_index("ix_messages_agent_created_at", "messages", ["agent_id", "created_at"])
op.create_index("ix_messages_created_at", "messages", ["created_at", "id"])
op.create_index("ix_messages_agent_sequence", "messages", ["agent_id", "sequence_id"])
op.create_index("ix_messages_org_agent", "messages", ["organization_id", "agent_id"])
# Create sequence table for SQLite message sequence_id generation
op.create_table(
"message_sequence",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("next_val", sa.Integer(), nullable=False, server_default="1"),
sa.PrimaryKeyConstraint("id"),
)
# Initialize the sequence table with the next available sequence_id
op.execute("INSERT INTO message_sequence (id, next_val) VALUES (1, 1)")
# Now create the rest of the tables that might reference messages/steps
# Add missing tables and columns identified from alembic check
# Identities table
op.create_table(
"identities",
sa.Column("id", sa.String(), nullable=False),
sa.Column("identifier_key", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("identity_type", sa.String(), nullable=False),
sa.Column("project_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("properties", sa.JSON(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.UniqueConstraint("identifier_key", "project_id", "organization_id", name="unique_identifier_key_project_id_organization_id"),
)
# MCP Server table
op.create_table(
"mcp_server",
sa.Column("id", sa.String(), nullable=False),
sa.Column("server_name", sa.String(), nullable=False),
sa.Column("server_type", sa.String(), nullable=False),
sa.Column("server_url", sa.String(), nullable=True),
sa.Column("stdio_config", sa.JSON(), nullable=True),
sa.Column("token", sa.String(), nullable=True),
sa.Column("custom_headers", sa.JSON(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("metadata_", sa.JSON(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.UniqueConstraint("server_name", "organization_id", name="uix_name_organization_mcp_server"),
)
# Providers table
op.create_table(
"providers",
sa.Column("id", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("api_key", sa.String(), nullable=True),
sa.Column("access_key", sa.String(), nullable=True),
sa.Column("region", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("provider_type", sa.String(), nullable=True),
sa.Column("base_url", sa.String(), nullable=True),
sa.Column("provider_category", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.UniqueConstraint("name", "organization_id", name="unique_name_organization_id"),
)
# Agent environment variables table
op.create_table(
"agent_environment_variables",
sa.Column("id", sa.String(), nullable=False),
sa.Column("key", sa.String(), nullable=False),
sa.Column("value", sa.String(), nullable=False),
sa.Column("description", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("agent_id", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"], ondelete="CASCADE"),
sa.UniqueConstraint("key", "agent_id", name="uix_key_agent"),
)
op.create_index("idx_agent_environment_variables_agent_id", "agent_environment_variables", ["agent_id"])
# Groups table
op.create_table(
"groups",
sa.Column("id", sa.String(), nullable=False),
sa.Column("description", sa.String(), nullable=False),
sa.Column("manager_type", sa.String(), nullable=False),
sa.Column("manager_agent_id", sa.String(), nullable=True),
sa.Column("termination_token", sa.String(), nullable=True),
sa.Column("max_turns", sa.Integer(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("agent_ids", sa.JSON(), nullable=False),
sa.Column("sleeptime_agent_frequency", sa.Integer(), nullable=True),
sa.Column("turns_counter", sa.Integer(), nullable=True),
sa.Column("last_processed_message_id", sa.String(), nullable=True),
sa.Column("max_message_buffer_length", sa.Integer(), nullable=True),
sa.Column("min_message_buffer_length", sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.ForeignKeyConstraint(["manager_agent_id"], ["agents.id"], ondelete="RESTRICT"),
)
# Steps table
op.create_table(
"steps",
sa.Column("id", sa.String(), nullable=False),
sa.Column("job_id", sa.String(), nullable=True),
sa.Column("completion_tokens", sa.Integer(), nullable=False, default=0),
sa.Column("prompt_tokens", sa.Integer(), nullable=False, default=0),
sa.Column("total_tokens", sa.Integer(), nullable=False, default=0),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("origin", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=True),
sa.Column("provider_id", sa.String(), nullable=True),
sa.Column("provider_name", sa.String(), nullable=True),
sa.Column("model", sa.String(), nullable=True),
sa.Column("context_window_limit", sa.Integer(), nullable=True),
sa.Column("completion_tokens_details", sa.JSON(), nullable=True),
sa.Column("tags", sa.JSON(), nullable=True),
sa.Column("tid", sa.String(), nullable=True),
sa.Column("model_endpoint", sa.String(), nullable=True),
sa.Column("trace_id", sa.String(), nullable=True),
sa.Column("agent_id", sa.String(), nullable=True),
sa.Column("provider_category", sa.String(), nullable=True),
sa.Column("feedback", sa.String(), nullable=True),
sa.Column("project_id", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["job_id"], ["jobs.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["provider_id"], ["providers.id"], ondelete="RESTRICT"),
)
# Note: Foreign key constraint for block.current_history_entry_id -> block_history.id
# would need to be added here, but SQLite doesn't support ALTER TABLE ADD CONSTRAINT
# This will be handled by the ORM at runtime
# Add missing columns to existing tables
# All missing columns have been added to the table definitions above
# step_id was already added in the messages table creation above
# op.add_column('messages', sa.Column('step_id', sa.String(), nullable=True))
# op.create_foreign_key('fk_messages_step_id', 'messages', 'steps', ['step_id'], ['id'], ondelete='SET NULL')
# Add index to source_passages for file_id
op.create_index("source_passages_file_id_idx", "source_passages", ["file_id"])
# Unique constraint for sources was added during table creation above
# Create remaining association tables
# Identities agents table
op.create_table(
"identities_agents",
sa.Column("identity_id", sa.String(), nullable=False),
sa.Column("agent_id", sa.String(), nullable=False),
sa.ForeignKeyConstraint(["identity_id"], ["identities.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("identity_id", "agent_id"),
)
# Identities blocks table
op.create_table(
"identities_blocks",
sa.Column("identity_id", sa.String(), nullable=False),
sa.Column("block_id", sa.String(), nullable=False),
sa.ForeignKeyConstraint(["identity_id"], ["identities.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["block_id"], ["block.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("identity_id", "block_id"),
)
# Files agents table
op.create_table(
"files_agents",
sa.Column("id", sa.String(), nullable=False),
sa.Column("file_id", sa.String(), nullable=False),
sa.Column("agent_id", sa.String(), nullable=False),
sa.Column("source_id", sa.String(), nullable=False),
sa.Column("is_open", sa.Boolean(), nullable=False),
sa.Column("visible_content", sa.Text(), nullable=True),
sa.Column("last_accessed_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("file_name", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id", "file_id", "agent_id"),
sa.ForeignKeyConstraint(["file_id"], ["files.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["source_id"], ["sources.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.UniqueConstraint("file_id", "agent_id", name="uq_file_agent"),
sa.UniqueConstraint("agent_id", "file_name", name="uq_agent_filename"),
)
op.create_index("ix_agent_filename", "files_agents", ["agent_id", "file_name"])
op.create_index("ix_file_agent", "files_agents", ["file_id", "agent_id"])
# Groups agents table
op.create_table(
"groups_agents",
sa.Column("group_id", sa.String(), nullable=False),
sa.Column("agent_id", sa.String(), nullable=False),
sa.ForeignKeyConstraint(["group_id"], ["groups.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("group_id", "agent_id"),
)
# Groups blocks table
op.create_table(
"groups_blocks",
sa.Column("group_id", sa.String(), nullable=False),
sa.Column("block_id", sa.String(), nullable=False),
sa.ForeignKeyConstraint(["group_id"], ["groups.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["block_id"], ["block.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("group_id", "block_id"),
)
# LLM batch job table
op.create_table(
"llm_batch_job",
sa.Column("id", sa.String(), nullable=False),
sa.Column("status", sa.String(), nullable=False),
sa.Column("llm_provider", sa.String(), nullable=False),
sa.Column("create_batch_response", sa.JSON(), nullable=False),
sa.Column("latest_polling_response", sa.JSON(), nullable=True),
sa.Column("last_polled_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("letta_batch_job_id", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.ForeignKeyConstraint(["letta_batch_job_id"], ["jobs.id"], ondelete="CASCADE"),
)
op.create_index("ix_llm_batch_job_created_at", "llm_batch_job", ["created_at"])
op.create_index("ix_llm_batch_job_status", "llm_batch_job", ["status"])
# LLM batch items table
op.create_table(
"llm_batch_items",
sa.Column("id", sa.String(), nullable=False),
sa.Column("llm_config", sa.JSON(), nullable=False),
sa.Column("request_status", sa.String(), nullable=False),
sa.Column("step_status", sa.String(), nullable=False),
sa.Column("step_state", sa.JSON(), nullable=False),
sa.Column("batch_request_result", sa.JSON(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.Column("agent_id", sa.String(), nullable=False),
sa.Column("llm_batch_id", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
sa.ForeignKeyConstraint(["agent_id"], ["agents.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["llm_batch_id"], ["llm_batch_job.id"], ondelete="CASCADE"),
)
op.create_index("ix_llm_batch_items_agent_id", "llm_batch_items", ["agent_id"])
op.create_index("ix_llm_batch_items_llm_batch_id", "llm_batch_items", ["llm_batch_id"])
op.create_index("ix_llm_batch_items_status", "llm_batch_items", ["request_status"])
# Job messages table
op.create_table(
"job_messages",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("job_id", sa.String(), nullable=False),
sa.Column("message_id", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.ForeignKeyConstraint(["job_id"], ["jobs.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["message_id"], ["messages.id"], ondelete="CASCADE"),
sa.UniqueConstraint("job_id", "message_id", name="unique_job_message"),
)
# File contents table
op.create_table(
"file_contents",
sa.Column("file_id", sa.String(), nullable=False),
sa.Column("text", sa.Text(), nullable=False),
sa.Column("id", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("file_id", "id"),
sa.ForeignKeyConstraint(["file_id"], ["files.id"], ondelete="CASCADE"),
sa.UniqueConstraint("file_id", name="uq_file_contents_file_id"),
)
# Provider traces table
op.create_table(
"provider_traces",
sa.Column("id", sa.String(), nullable=False),
sa.Column("request_json", sa.JSON(), nullable=False),
sa.Column("response_json", sa.JSON(), nullable=False),
sa.Column("step_id", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=True),
sa.Column("is_deleted", sa.Boolean(), server_default=sa.text("(FALSE)"), nullable=False),
sa.Column("_created_by_id", sa.String(), nullable=True),
sa.Column("_last_updated_by_id", sa.String(), nullable=True),
sa.Column("organization_id", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["organization_id"], ["organizations.id"]),
)
op.create_index("ix_step_id", "provider_traces", ["step_id"])
# Complete the SQLite schema alignment by adding any remaining missing elements
try:
# Unique constraints for files_agents are already created with correct names in table definition above
# Foreign key for files_agents.source_id is already created in table definition above
# Foreign key for messages.step_id is already created in table definition above
pass
except Exception:
# Some operations may fail if the column/constraint already exists
# This is expected in some cases and we can continue
pass
# Note: The remaining alembic check differences are expected for SQLite:
# 1. Type differences (BLOB vs CommonVector) - Expected and handled by ORM
# 2. Foreign key constraint differences - SQLite handles these at runtime
# 3. Index differences - SQLite doesn't support all PostgreSQL index features
# 4. Some constraint naming differences - Cosmetic differences
#
# These differences do not affect functionality as the ORM handles the abstraction
# between SQLite and PostgreSQL appropriately.
def downgrade() -> None:
# Only run this migration for SQLite
if settings.letta_pg_uri_no_default:
return
# SQLite downgrade is not supported
raise NotImplementedError("SQLite downgrade is not supported. Use a fresh database instead.")