# app/models/memory/procedure.py """ Procedure database model. Stores procedural memories - learned skills and procedures derived from successful task execution patterns. """ from sqlalchemy import ( CheckConstraint, Column, DateTime, ForeignKey, Index, Integer, String, Text, ) from sqlalchemy.dialects.postgresql import ( JSONB, UUID as PGUUID, ) from sqlalchemy.orm import relationship from app.models.base import Base, TimestampMixin, UUIDMixin # Import pgvector type try: from pgvector.sqlalchemy import Vector # type: ignore[import-not-found] except ImportError: Vector = None class Procedure(Base, UUIDMixin, TimestampMixin): """ Procedural memory model. Stores learned procedures (skills) extracted from successful task execution patterns: - Name and trigger pattern for matching - Step-by-step actions - Success/failure tracking """ __tablename__ = "procedures" # Scoping project_id = Column( PGUUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=True, index=True, ) agent_type_id = Column( PGUUID(as_uuid=True), ForeignKey("agent_types.id", ondelete="SET NULL"), nullable=True, index=True, ) # Procedure identification name = Column(String(255), nullable=False, index=True) trigger_pattern = Column(Text, nullable=False) # Steps as JSON array of step objects # Each step: {order, action, parameters, expected_outcome, fallback_action} steps = Column(JSONB, default=list, nullable=False) # Success tracking success_count = Column(Integer, nullable=False, default=0) failure_count = Column(Integer, nullable=False, default=0) # Usage tracking last_used = Column(DateTime(timezone=True), nullable=True, index=True) # Vector embedding for semantic matching embedding = Column(Vector(1536) if Vector else Text, nullable=True) # Relationships project = relationship("Project", foreign_keys=[project_id]) agent_type = relationship("AgentType", foreign_keys=[agent_type_id]) __table_args__ = ( # Unique procedure name within scope Index( "ix_procedures_unique_name", "project_id", "agent_type_id", "name", unique=True, ), # Query patterns Index("ix_procedures_project_name", "project_id", "name"), # Note: agent_type_id already has index=True on Column definition # For finding best procedures Index("ix_procedures_success_rate", "success_count", "failure_count"), # Data integrity constraints CheckConstraint( "success_count >= 0", name="ck_procedures_success_positive", ), CheckConstraint( "failure_count >= 0", name="ck_procedures_failure_positive", ), ) @property def success_rate(self) -> float: """Calculate the success rate of this procedure.""" # Snapshot values to avoid race conditions in concurrent access success = self.success_count failure = self.failure_count total = success + failure if total == 0: return 0.0 return success / total @property def total_uses(self) -> int: """Get total number of times this procedure was used.""" # Snapshot values for consistency return self.success_count + self.failure_count def __repr__(self) -> str: return ( f"" )