Wenchao An 0b3dadbc9b
feat(subagents): add acceptance checks to durable batch items (#5289)
* feat(subagents): check and persist durable batch acceptance

Carry optional per-item criteria into native subagents, reuse the deterministic checker, and expose separate verdicts through item queries and exports. Preserve execution and retry semantics, renew leases during checks, and migrate existing batch rows with nullable acceptance fields.

* fix(subagents): align batch acceptance normalization and sandbox admission

* test(auth): include project permissions in the full-stack contract
2026-09-09 08:45:20 +08:00

74 lines
3.9 KiB
Python

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"),
)