mirror of
https://github.com/himanshudongre/smriti.git
synced 2026-09-09 22:31:07 +00:00
Smriti can now run against a file-backed SQLite database with no Docker and no Postgres, removing the operate-a-backend burden for solo builders. Postgres remains the stronger shared/team mode, unchanged. - Portable column types (app/db/types.py): JSON renders as JSONB on PostgreSQL and generic JSON on SQLite; the pgvector embedding column renders as JSON on SQLite. Same models and create_all on both backends. - Mode resolution (config.py): SMRITI_DB_MODE=local|postgres, defaulting to local when unconfigured. An explicitly-set Postgres DATABASE_URL preserves Postgres behavior, so existing setups are unaffected. Local DB defaults to ~/.smriti/smriti.db; SMRITI_LOCAL_DB_PATH overrides. - SQLite engine setup (database.py): check_same_thread plus foreign_keys / WAL / busy_timeout pragmas; a lazy first-run create_all bootstrap on first DB use, which keeps the integration test suite insulated. - Removed the integration-test JSONB/VECTOR DDL substitution hack — the models are genuinely portable now, so conftest needs no type patching. - Alembic resolves its URL through the same logic (Postgres mode only). - New persistent file-backed SQLite smoke test, plus mode-resolution and per-dialect type-rendering tests. Local mode uses create_all, not Alembic. No schema changes.
352 lines
16 KiB
Python
352 lines
16 KiB
Python
"""SQLAlchemy ORM models for all database tables."""
|
|
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import DateTime, Float, ForeignKey, Integer, String, Text
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.db.database import Base
|
|
from app.db.types import embedding_column, json_column
|
|
|
|
|
|
def _utcnow() -> datetime:
|
|
return datetime.now(timezone.utc)
|
|
|
|
|
|
class SessionModel(Base):
|
|
__tablename__ = "sessions"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
title: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
source_tool: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
|
raw_transcript: Mapped[str] = mapped_column(Text, nullable=False)
|
|
status: Mapped[str] = mapped_column(String(20), default="processing")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
|
)
|
|
|
|
# Relationships
|
|
messages: Mapped[list["MessageModel"]] = relationship(
|
|
back_populates="session", cascade="all, delete-orphan", order_by="MessageModel.position"
|
|
)
|
|
extraction_result: Mapped["ExtractionResultModel | None"] = relationship(
|
|
back_populates="session", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
context_packs: Mapped[list["ContextPackModel"]] = relationship(
|
|
back_populates="session", cascade="all, delete-orphan"
|
|
)
|
|
|
|
|
|
class MessageModel(Base):
|
|
__tablename__ = "messages"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
session_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("sessions.id", ondelete="CASCADE"), nullable=False
|
|
)
|
|
role: Mapped[str] = mapped_column(String(20), nullable=False)
|
|
content: Mapped[str] = mapped_column(Text, nullable=False)
|
|
position: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
|
|
|
# Relationships
|
|
session: Mapped["SessionModel"] = relationship(back_populates="messages")
|
|
|
|
|
|
class ExtractionResultModel(Base):
|
|
__tablename__ = "extraction_results"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
session_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("sessions.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
unique=True,
|
|
)
|
|
summary: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
decisions: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
tasks: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
open_questions: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
entities: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
code_snippets: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
|
|
|
# Relationships
|
|
session: Mapped["SessionModel"] = relationship(back_populates="extraction_result")
|
|
|
|
|
|
class ContextPackModel(Base):
|
|
__tablename__ = "context_packs"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
session_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("sessions.id", ondelete="CASCADE"), nullable=False
|
|
)
|
|
target_tool: Mapped[str] = mapped_column(String(20), nullable=False)
|
|
content: Mapped[str] = mapped_column(Text, nullable=False)
|
|
format: Mapped[str] = mapped_column(String(20), default="markdown")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
|
|
|
# Relationships
|
|
session: Mapped["SessionModel"] = relationship(back_populates="context_packs")
|
|
|
|
|
|
class MemoryItemModel(Base):
|
|
__tablename__ = "memories"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), nullable=False, index=True
|
|
)
|
|
type: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
content: Mapped[str] = mapped_column(Text, nullable=False)
|
|
embedding: Mapped[list[float] | None] = mapped_column(embedding_column(1536), nullable=True) # OpenAI small model dim
|
|
source: Mapped[str] = mapped_column(String(255), nullable=True)
|
|
confidence: Mapped[float] = mapped_column(Float, default=1.0)
|
|
importance: Mapped[float] = mapped_column(Float, default=1.0)
|
|
status: Mapped[str] = mapped_column(String(50), default="active")
|
|
metadata_: Mapped[dict] = mapped_column("metadata", json_column(), default=dict)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
|
)
|
|
|
|
class RepoModel(Base):
|
|
__tablename__ = "repos"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
repo_slug: Mapped[str | None] = mapped_column(String(255), unique=True, nullable=True)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str] = mapped_column(Text, default="")
|
|
project_root: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), nullable=False, index=True
|
|
)
|
|
metadata_: Mapped[dict] = mapped_column("metadata", json_column(), default=dict)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
|
)
|
|
|
|
# Relationships
|
|
commits: Mapped[list["CommitModel"]] = relationship(
|
|
back_populates="repo", cascade="all, delete-orphan", order_by="CommitModel.created_at.desc()"
|
|
)
|
|
worktrees: Mapped[list["WorkTree"]] = relationship(
|
|
back_populates="repo", cascade="all, delete-orphan"
|
|
)
|
|
|
|
class CommitModel(Base):
|
|
__tablename__ = "commits"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
repo_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("repos.id", ondelete="CASCADE"), nullable=False
|
|
)
|
|
commit_hash: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
|
|
parent_commit_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("commits.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
branch_name: Mapped[str] = mapped_column(String(255), default="main")
|
|
author_agent: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
author_type: Mapped[str] = mapped_column(String(50), default="user") # user, llm, agent, system
|
|
project_root: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
|
message: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
# State snapshots
|
|
summary: Mapped[str] = mapped_column(Text, default="")
|
|
objective: Mapped[str] = mapped_column(Text, default="")
|
|
decisions: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
assumptions: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
tasks: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
open_questions: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
entities: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
artifacts: Mapped[dict] = mapped_column(json_column(), default=list)
|
|
context_blob: Mapped[dict] = mapped_column(json_column(), default=dict)
|
|
|
|
raw_source_text: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
metadata_: Mapped[dict] = mapped_column("metadata", json_column(), default=dict)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
|
|
|
# Relationships
|
|
repo: Mapped["RepoModel"] = relationship(back_populates="commits")
|
|
parent_commit: Mapped["CommitModel"] = relationship(remote_side=[id])
|
|
|
|
|
|
# ── V4 Chat Models ────────────────────────────────────────────────────────────
|
|
|
|
class ChatSession(Base):
|
|
"""A live conversation runtime inside a Space (Repo)."""
|
|
__tablename__ = "chat_sessions"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
repo_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("repos.id", ondelete="CASCADE"), nullable=True, index=True
|
|
)
|
|
title: Mapped[str] = mapped_column(String(255), default="")
|
|
active_provider: Mapped[str] = mapped_column(String(50), default="openrouter")
|
|
active_model: Mapped[str] = mapped_column(String(255), default="")
|
|
# The commit that was used to seed this session at open time
|
|
seeded_commit_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("commits.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
# Forking: which checkpoint this session branched from (None = not a fork)
|
|
forked_from_checkpoint_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("commits.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
# Branch identity — "main" for primary sessions, custom name for forks
|
|
branch_name: Mapped[str] = mapped_column(String(255), default="main")
|
|
# Branch lifecycle — explicit disposition signal so agents and humans
|
|
# can mark a branch as integrated or abandoned, removing it from the
|
|
# ## Active branches section of smriti state without deleting history.
|
|
# Values: "active" (default, shown), "integrated" (hidden), "abandoned" (hidden).
|
|
branch_disposition: Mapped[str] = mapped_column(String(20), default="active")
|
|
metadata_: Mapped[dict] = mapped_column("metadata", json_column(), default=dict)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
|
)
|
|
|
|
# Relationships
|
|
turns: Mapped[list["TurnEvent"]] = relationship(
|
|
back_populates="session",
|
|
cascade="all, delete-orphan",
|
|
order_by="TurnEvent.sequence_number",
|
|
)
|
|
|
|
|
|
class TurnEvent(Base):
|
|
"""One user or assistant message turn inside a ChatSession."""
|
|
__tablename__ = "turn_events"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
session_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("chat_sessions.id", ondelete="CASCADE"),
|
|
nullable=False, index=True
|
|
)
|
|
repo_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("repos.id", ondelete="CASCADE"),
|
|
nullable=True, index=True
|
|
)
|
|
role: Mapped[str] = mapped_column(String(20), nullable=False) # user | assistant | system
|
|
content: Mapped[str] = mapped_column(Text, nullable=False)
|
|
provider: Mapped[str] = mapped_column(String(50), default="")
|
|
model: Mapped[str] = mapped_column(String(255), default="")
|
|
sequence_number: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
# Optional back-link to a commit that summarises this turn range
|
|
commit_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("commits.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
metadata_: Mapped[dict] = mapped_column("metadata", json_column(), default=dict)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
|
|
|
# Relationships
|
|
session: Mapped["ChatSession"] = relationship(back_populates="turns")
|
|
|
|
|
|
class WorkClaim(Base):
|
|
"""A lightweight, time-bounded declaration that an agent is actively
|
|
working on something in a space.
|
|
|
|
Work claims are advisory, not locks. They make active intent visible
|
|
to other agents before work produces a checkpoint. The skill pack
|
|
teaches agents to check for overlapping claims before starting work
|
|
and to create their own claim after reading state.
|
|
|
|
Claims expire at `expires_at`. Expired claims are excluded from the
|
|
state brief by query-time filtering — no background sweep needed.
|
|
Agents explicitly mark claims as `done` or `abandoned` when work
|
|
finishes; if they forget, the claim expires naturally.
|
|
"""
|
|
__tablename__ = "work_claims"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
repo_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("repos.id", ondelete="CASCADE"),
|
|
nullable=False, index=True,
|
|
)
|
|
session_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("chat_sessions.id", ondelete="SET NULL"),
|
|
nullable=True,
|
|
)
|
|
agent: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
branch_name: Mapped[str] = mapped_column(String(255), default="main")
|
|
base_commit_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("commits.id", ondelete="SET NULL"),
|
|
nullable=True,
|
|
)
|
|
worktree_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("work_trees.id", ondelete="SET NULL"),
|
|
nullable=True, index=True,
|
|
)
|
|
scope: Mapped[str] = mapped_column(Text, nullable=False)
|
|
task_id: Mapped[str | None] = mapped_column(
|
|
String(100), nullable=True,
|
|
doc="Optional reference to a structured task's id field in the checkpoint",
|
|
)
|
|
intent_type: Mapped[str] = mapped_column(
|
|
String(20), default="implement",
|
|
doc="One of: implement, review, investigate, docs, test",
|
|
)
|
|
status: Mapped[str] = mapped_column(
|
|
String(20), default="active",
|
|
doc="One of: active, done, abandoned",
|
|
)
|
|
claimed_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=_utcnow,
|
|
)
|
|
expires_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False,
|
|
)
|
|
|
|
worktree: Mapped["WorkTree | None"] = relationship(back_populates="claims")
|
|
|
|
|
|
class WorkTree(Base):
|
|
"""A git worktree allocated for an agent working in a space.
|
|
|
|
Worktrees provide filesystem-level isolation: separate working
|
|
directories and git indexes backed by the same repository object store.
|
|
"""
|
|
__tablename__ = "work_trees"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
repo_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("repos.id", ondelete="CASCADE"),
|
|
nullable=False, index=True,
|
|
)
|
|
agent: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
path: Mapped[str] = mapped_column(Text, nullable=False)
|
|
branch_name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
base_commit_sha: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(20), default="active")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
|
closed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
repo: Mapped["RepoModel"] = relationship(back_populates="worktrees")
|
|
claims: Mapped[list["WorkClaim"]] = relationship(back_populates="worktree")
|