Files
nihalashetty ae67bff5a3 feat: deep-agent canvas, live observability, and multi-environment tooling
Self-hosted platform for building, testing, and shipping LangChain/LangGraph agents. Deep-agent sub-agents on the canvas, a live tracing/observability timeline, auto-provisioned built-in tools with import/export, per-environment tool variables, streamed evaluations, and per-user auth token forwarding.
2026-07-28 01:49:19 +05:30

92 lines
3.9 KiB
Python

"""tool sets + membership
Adds first-class Tool Sets (describable groups of tools) and their many-to-many membership
join, introduced by the tool-sets / MCP-toolsets work. `create_all` builds these in dev;
managed Postgres needs this migration.
- tool_sets (name, slug, description, icon, is_default; unique slug per project)
- tool_set_members (tool_set_id <-> tool_id, unique per pair)
Column types / nullability mirror the ORM's `create_all` output so a dev SQLite database and
a migrated Postgres database converge. NOT NULL columns that carry an ORM-side default also
get a `server_default` (mirrors 0004) so the tables are insertable via raw SQL.
Idempotent: each table is created only when absent, so this is safe whether the DB was
created fresh by `create_all` (tables already present) or predates the feature. Tenant-
isolation RLS policies for these tables live in infra/postgres_rls.sql.
Revision ID: 0007_tool_sets
Revises: 0006_drop_tool_agent_version
Create Date: 2026-07-15
"""
import sqlalchemy as sa
from alembic import op
revision = "0007_tool_sets"
down_revision = "0006_drop_tool_agent_version"
branch_labels = None
depends_on = None
def _inspector():
return sa.inspect(op.get_bind())
def _has_table(insp, name: str) -> bool:
return name in insp.get_table_names()
def _pk_timestamp_cols() -> list[sa.Column]:
# Mirrors db.base.PkTimestamp: string-UUID PK + created/updated timestamps (nullable to
# match the 0002/0003/0004 precedent; the ORM populates them on every insert).
return [
sa.Column("id", sa.String(36), primary_key=True),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("updated_at", sa.DateTime(), nullable=True),
]
def upgrade() -> None:
insp = _inspector()
# --- tool_sets -----------------------------------------------------------------------
if not _has_table(insp, "tool_sets"):
op.create_table(
"tool_sets",
*_pk_timestamp_cols(),
sa.Column("tenant_id", sa.String(36), nullable=False),
sa.Column("project_id", sa.String(36), nullable=False),
sa.Column("name", sa.String(120), nullable=False),
sa.Column("slug", sa.String(120), nullable=False),
sa.Column("description", sa.Text(), server_default="", nullable=False),
sa.Column("icon", sa.String(60), nullable=True),
sa.Column("is_default", sa.Boolean(), server_default=sa.false(), nullable=False),
sa.UniqueConstraint("tenant_id", "project_id", "slug", name="uq_tool_set_slug"),
)
op.create_index("ix_tool_sets_tenant_id", "tool_sets", ["tenant_id"])
op.create_index("ix_tool_sets_project_id", "tool_sets", ["project_id"])
op.create_index("ix_tool_sets_slug", "tool_sets", ["slug"])
# --- tool_set_members ----------------------------------------------------------------
if not _has_table(insp, "tool_set_members"):
op.create_table(
"tool_set_members",
*_pk_timestamp_cols(),
sa.Column("tenant_id", sa.String(36), nullable=False),
sa.Column("project_id", sa.String(36), nullable=False),
sa.Column("tool_set_id", sa.String(36), nullable=False),
sa.Column("tool_id", sa.String(36), nullable=False),
sa.UniqueConstraint("tool_set_id", "tool_id", name="uq_tool_set_member"),
)
op.create_index("ix_tool_set_members_tenant_id", "tool_set_members", ["tenant_id"])
op.create_index("ix_tool_set_members_project_id", "tool_set_members", ["project_id"])
op.create_index("ix_tool_set_members_tool_set_id", "tool_set_members", ["tool_set_id"])
op.create_index("ix_tool_set_members_tool_id", "tool_set_members", ["tool_id"])
def downgrade() -> None:
insp = _inspector()
for table in ("tool_set_members", "tool_sets"):
if _has_table(insp, table):
op.drop_table(table)