# app/models/memory/episode.py """ Episode database model. Stores experiential memories - records of past task executions with context, actions, outcomes, and lessons learned. """ from sqlalchemy import ( BigInteger, CheckConstraint, Column, DateTime, Enum, Float, ForeignKey, Index, String, Text, ) from sqlalchemy.dialects.postgresql import ( JSONB, UUID as PGUUID, ) from sqlalchemy.orm import relationship from app.models.base import Base, TimestampMixin, UUIDMixin from .enums import EpisodeOutcome # Import pgvector type - will be available after migration enables extension try: from pgvector.sqlalchemy import Vector # type: ignore[import-not-found] except ImportError: # Fallback for environments without pgvector Vector = None class Episode(Base, UUIDMixin, TimestampMixin): """ Episodic memory model. Records experiential memories from agent task execution: - What task was performed - What actions were taken - What was the outcome - What lessons were learned """ __tablename__ = "episodes" # Foreign keys project_id = Column( PGUUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True, ) agent_instance_id = Column( PGUUID(as_uuid=True), ForeignKey("agent_instances.id", ondelete="SET NULL"), nullable=True, index=True, ) agent_type_id = Column( PGUUID(as_uuid=True), ForeignKey("agent_types.id", ondelete="SET NULL"), nullable=True, index=True, ) # Session reference session_id = Column(String(255), nullable=False, index=True) # Task information task_type = Column(String(100), nullable=False, index=True) task_description = Column(Text, nullable=False) # Actions taken (list of action dictionaries) actions = Column(JSONB, default=list, nullable=False) # Context summary context_summary = Column(Text, nullable=False) # Outcome outcome: Column[EpisodeOutcome] = Column( Enum(EpisodeOutcome), nullable=False, index=True, ) outcome_details = Column(Text, nullable=True) # Metrics duration_seconds = Column(Float, nullable=False, default=0.0) tokens_used = Column(BigInteger, nullable=False, default=0) # Learning lessons_learned = Column(JSONB, default=list, nullable=False) importance_score = Column(Float, nullable=False, default=0.5, index=True) # Vector embedding for semantic search # Using 1536 dimensions for OpenAI text-embedding-3-small embedding = Column(Vector(1536) if Vector else Text, nullable=True) # When the episode occurred occurred_at = Column(DateTime(timezone=True), nullable=False, index=True) # Relationships project = relationship("Project", foreign_keys=[project_id]) agent_instance = relationship("AgentInstance", foreign_keys=[agent_instance_id]) agent_type = relationship("AgentType", foreign_keys=[agent_type_id]) __table_args__ = ( # Primary query patterns Index("ix_episodes_project_task", "project_id", "task_type"), Index("ix_episodes_project_outcome", "project_id", "outcome"), Index("ix_episodes_agent_task", "agent_instance_id", "task_type"), Index("ix_episodes_project_time", "project_id", "occurred_at"), # For importance-based pruning Index("ix_episodes_importance_time", "importance_score", "occurred_at"), # Data integrity constraints CheckConstraint( "importance_score >= 0.0 AND importance_score <= 1.0", name="ck_episodes_importance_range", ), CheckConstraint( "duration_seconds >= 0.0", name="ck_episodes_duration_positive", ), CheckConstraint( "tokens_used >= 0", name="ck_episodes_tokens_positive", ), ) def __repr__(self) -> str: return f""