from __future__ import annotations from datetime import UTC, datetime from typing import Any from sqlalchemy import JSON, DateTime, ForeignKey, Index, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from deerflow.persistence.base import Base class SubagentBatchRow(Base): __tablename__ = "subagent_batches" id: Mapped[str] = mapped_column(String(64), primary_key=True) user_id: Mapped[str] = mapped_column(String(64), index=True) thread_id: Mapped[str] = mapped_column(String(64), index=True) run_id: Mapped[str | None] = mapped_column(String(64), nullable=True) tool_call_id: Mapped[str | None] = mapped_column(String(128), nullable=True) submission_key: Mapped[str] = mapped_column(String(256)) title: Mapped[str] = mapped_column(String(256)) subagent_type: Mapped[str] = mapped_column(String(128)) status: Mapped[str] = mapped_column(String(24), index=True) total_items: Mapped[int] = mapped_column(Integer) max_live_items: Mapped[int] = mapped_column(Integer) max_running_items: Mapped[int] = mapped_column(Integer) max_attempts: Mapped[int] = mapped_column(Integer) execution_spec: Mapped[dict[str, Any]] = mapped_column(JSON) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC)) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC)) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) __table_args__ = ( UniqueConstraint("user_id", "submission_key", name="uq_subagent_batches_user_submission"), Index("ix_subagent_batches_thread_created", "thread_id", "created_at"), ) class SubagentBatchItemRow(Base): __tablename__ = "subagent_batch_items" id: Mapped[str] = mapped_column(String(64), primary_key=True) batch_id: Mapped[str] = mapped_column( ForeignKey("subagent_batches.id", ondelete="CASCADE"), index=True, ) item_key: Mapped[str] = mapped_column(String(128)) position: Mapped[int] = mapped_column(Integer) prompt: Mapped[str] = mapped_column(Text) acceptance_criteria: Mapped[list[str] | None] = mapped_column(JSON, nullable=True) acceptance_verdict: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) status: Mapped[str] = mapped_column(String(24), index=True) attempt: Mapped[int] = mapped_column(Integer, default=0) lease_owner: Mapped[str | None] = mapped_column(String(128), nullable=True) lease_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) cancel_requested_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) model_name: Mapped[str | None] = mapped_column(String(128), nullable=True) result: Mapped[str | None] = mapped_column(Text, nullable=True) result_preview: Mapped[str | None] = mapped_column(Text, nullable=True) result_truncated: Mapped[bool] = mapped_column(default=False) error: Mapped[str | None] = mapped_column(Text, nullable=True) stop_reason: Mapped[str | None] = mapped_column(String(64), nullable=True) token_usage: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC)) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC)) __table_args__ = ( UniqueConstraint("batch_id", "item_key", name="uq_subagent_batch_items_key"), UniqueConstraint("batch_id", "position", name="uq_subagent_batch_items_position"), Index("ix_subagent_batch_items_claim", "status", "lease_expires_at", "batch_id"), )