from __future__ import annotations import uuid from django.core.exceptions import ValidationError from django.db import models from control_plane.common import TimestampedModel class AgentRole(models.TextChoices): PROJECT_ARCHAEOLOGIST = "PROJECT_ARCHAEOLOGIST" PLANNER = "PLANNER" CODER = "CODER" REVIEWER = "REVIEWER" PROJECT_JUDGE = "PROJECT_JUDGE" FRONTEND_DESIGNER = "FRONTEND_DESIGNER" FRONTEND_ENGINEER = "FRONTEND_ENGINEER" UX_ACCESSIBILITY_REVIEWER = "UX_ACCESSIBILITY_REVIEWER" VISUAL_JUDGE = "VISUAL_JUDGE" class PromotionStatus(models.TextChoices): DRAFT = "DRAFT" CANDIDATE = "CANDIDATE" CHALLENGER = "CHALLENGER" CHAMPION = "CHAMPION" REJECTED = "REJECTED" RETIRED = "RETIRED" DISABLED = "DISABLED" class AgentScope(models.TextChoices): GLOBAL = "GLOBAL" STUDIO = "STUDIO" PROJECT = "PROJECT" class Agent(TimestampedModel): name = models.CharField(max_length=200, unique=True) role = models.CharField(max_length=80, choices=AgentRole.choices) description = models.TextField(blank=True) purpose = models.TextField(blank=True) scope = models.CharField(max_length=32, choices=AgentScope.choices, default=AgentScope.GLOBAL) studio = models.CharField(max_length=120, blank=True) project = models.ForeignKey("projects.Project", on_delete=models.SET_NULL, null=True, blank=True, related_name="scoped_agents") status = models.CharField(max_length=32, default="ACTIVE") metadata = models.JSONField(default=dict, blank=True) champion_version = models.ForeignKey( "AgentVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="championed_by" ) class AgentVersion(TimestampedModel): agent = models.ForeignKey(Agent, on_delete=models.CASCADE, related_name="versions") version = models.PositiveIntegerField() model = models.CharField(max_length=120) model_config = models.JSONField(default=dict, blank=True) system_contract = models.TextField() capabilities = models.JSONField(default=list, blank=True) tools = models.JSONField(default=list, blank=True) permissions = models.JSONField(default=dict, blank=True) context_policy = models.JSONField(default=dict, blank=True) retrieval_policy = models.JSONField(default=dict, blank=True) tool_policy = models.JSONField(default=dict, blank=True) workflow = models.JSONField(default=dict, blank=True) retry_policy = models.JSONField(default=dict, blank=True) resource_preferences = models.JSONField(default=dict, blank=True) graph_usage_policy = models.JSONField(default=dict, blank=True) scope = models.CharField(max_length=32, choices=AgentScope.choices, default=AgentScope.GLOBAL) parent_version = models.ForeignKey("self", on_delete=models.SET_NULL, null=True, blank=True, related_name="children") ancestry = models.JSONField(default=list, blank=True) creation_source = models.CharField(max_length=80, default="seed") evaluator = models.JSONField(default=dict, blank=True) benchmark_status = models.CharField(max_length=32, default="UNBENCHMARKED") promotion_status = models.CharField(max_length=32, choices=PromotionStatus.choices, default=PromotionStatus.CANDIDATE) status = models.CharField(max_length=32, default="ACTIVE") immutable_since = models.DateTimeField(null=True, blank=True) metadata = models.JSONField(default=dict, blank=True) class Meta: constraints = [models.UniqueConstraint(fields=["agent", "version"], name="unique_agent_version")] def save(self, *args: object, **kwargs: object) -> None: if self.promotion_status == PromotionStatus.CHAMPION: existing = AgentVersion.objects.filter(agent=self.agent, promotion_status=PromotionStatus.CHAMPION).exclude(pk=self.pk) if existing.exists(): raise ValidationError("Only one champion AgentVersion is allowed per Agent.") super().save(*args, **kwargs) class AgentPlan(TimestampedModel): agent = models.ForeignKey(Agent, on_delete=models.CASCADE, related_name="plans", null=True, blank=True) name = models.CharField(max_length=200) role = models.CharField(max_length=80, choices=AgentRole.choices) model = models.CharField(max_length=120) system_contract = models.TextField() capabilities = models.JSONField(default=list, blank=True) tools = models.JSONField(default=list, blank=True) permissions = models.JSONField(default=dict, blank=True) context_policy = models.JSONField(default=dict, blank=True) workflow = models.JSONField(default=dict, blank=True) benchmarks = models.JSONField(default=list, blank=True) success_criteria = models.JSONField(default=dict, blank=True) target_agent_version = models.ForeignKey(AgentVersion, on_delete=models.SET_NULL, null=True, blank=True, related_name="plans_targeting_version") plan_type = models.CharField(max_length=32, default="CREATE") scope = models.CharField(max_length=32, choices=AgentScope.choices, default=AgentScope.GLOBAL) evidence = models.JSONField(default=dict, blank=True) status = models.CharField(max_length=32, default="DRAFT") created_by = models.CharField(max_length=120, default="sol") class Competency(TimestampedModel): key = models.CharField(max_length=120, unique=True) name = models.CharField(max_length=200) description = models.TextField(blank=True) domain = models.CharField(max_length=120, blank=True) metadata = models.JSONField(default=dict, blank=True) class AgentCompetency(TimestampedModel): agent = models.ForeignKey(Agent, on_delete=models.CASCADE, related_name="competencies", null=True, blank=True) agent_version = models.ForeignKey(AgentVersion, on_delete=models.CASCADE, related_name="competencies", null=True, blank=True) competency = models.ForeignKey(Competency, on_delete=models.CASCADE, related_name="agent_assignments") proficiency = models.FloatField(default=0.5) confidence = models.FloatField(default=0.5) evidence = models.JSONField(default=dict, blank=True) source = models.CharField(max_length=80, default="manual") last_evaluated_at = models.DateTimeField(null=True, blank=True) class AgentTeam(TimestampedModel): name = models.CharField(max_length=200) purpose = models.TextField(blank=True) scope = models.CharField(max_length=32, choices=AgentScope.choices, default=AgentScope.GLOBAL) status = models.CharField(max_length=32, default="ACTIVE") execution_graph_version = models.ForeignKey("graph.ExecutionGraphVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="agent_teams") metadata = models.JSONField(default=dict, blank=True) class AgentTeamMember(TimestampedModel): team = models.ForeignKey(AgentTeam, on_delete=models.CASCADE, related_name="members") agent_version = models.ForeignKey(AgentVersion, on_delete=models.CASCADE, related_name="team_memberships") role = models.CharField(max_length=120) responsibilities = models.JSONField(default=list, blank=True) status = models.CharField(max_length=32, default="ACTIVE") metadata = models.JSONField(default=dict, blank=True) class Meta: constraints = [models.UniqueConstraint(fields=["team", "agent_version", "role"], name="unique_agent_team_member_role")] class AgentRun(TimestampedModel): agent_version = models.ForeignKey(AgentVersion, on_delete=models.PROTECT, related_name="runs") project = models.ForeignKey("projects.Project", on_delete=models.CASCADE, null=True, blank=True) task = models.ForeignKey("projects.Task", on_delete=models.SET_NULL, null=True, blank=True) status = models.CharField(max_length=32, default="QUEUED") input_contract = models.JSONField(default=dict, blank=True) output_contract = models.JSONField(default=dict, blank=True) metrics = models.JSONField(default=dict, blank=True) class BenchmarkRun(TimestampedModel): champion = models.ForeignKey(AgentVersion, on_delete=models.PROTECT, related_name="champion_benchmarks") challenger = models.ForeignKey(AgentVersion, on_delete=models.PROTECT, related_name="challenger_benchmarks") benchmark_set = models.JSONField(default=list, blank=True) metrics = models.JSONField(default=dict, blank=True) decision = models.CharField(max_length=32, default="PENDING") class ProgenySignal(TimestampedModel): project = models.ForeignKey("projects.Project", on_delete=models.SET_NULL, null=True, blank=True) task = models.ForeignKey("projects.Task", on_delete=models.SET_NULL, null=True, blank=True) milestone = models.ForeignKey("projects.Milestone", on_delete=models.SET_NULL, null=True, blank=True) agent_version = models.ForeignKey(AgentVersion, on_delete=models.SET_NULL, null=True, blank=True) graph_run = models.ForeignKey("graph.GraphRun", on_delete=models.SET_NULL, null=True, blank=True) graph_node_run = models.ForeignKey("graph.GraphNodeRun", on_delete=models.SET_NULL, null=True, blank=True) execution_graph_version = models.ForeignKey("graph.ExecutionGraphVersion", on_delete=models.SET_NULL, null=True, blank=True) source = models.CharField(max_length=80) severity = models.CharField(max_length=32, default="INFO") failure_category = models.CharField(max_length=80, blank=True) summary = models.TextField() evidence = models.JSONField(default=dict, blank=True) status = models.CharField(max_length=32, default="OPEN") grouping_key = models.CharField(max_length=120, blank=True) model = models.CharField(max_length=120, blank=True) class ProgenyInvestigation(TimestampedModel): status = models.CharField(max_length=32, default="OPEN") signals = models.ManyToManyField(ProgenySignal, related_name="investigations", blank=True) signal_clusters = models.JSONField(default=list, blank=True) affected_projects = models.JSONField(default=list, blank=True) affected_agents = models.JSONField(default=list, blank=True) affected_graph_versions = models.JSONField(default=list, blank=True) affected_nodes = models.JSONField(default=list, blank=True) hypotheses = models.JSONField(default=list, blank=True) recommended_target = models.CharField(max_length=80) confidence = models.FloatField(default=0.0) recommended_route = models.CharField(max_length=120) proposed_experiments = models.JSONField(default=list, blank=True) expected_impact = models.TextField(blank=True) estimated_cost = models.CharField(max_length=80, blank=True) class ImprovementCandidate(TimestampedModel): status = models.CharField(max_length=32, default="PROPOSED") target_type = models.CharField(max_length=80) target_id = models.CharField(max_length=120, blank=True) target_label = models.CharField(max_length=240, blank=True) hypothesis = models.TextField() recommended_route = models.CharField(max_length=120, blank=True) evidence = models.JSONField(default=dict, blank=True) investigation = models.ForeignKey(ProgenyInvestigation, on_delete=models.SET_NULL, null=True, blank=True, related_name="improvement_candidates") execution_graph_version = models.ForeignKey("graph.ExecutionGraphVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="improvement_candidates") agent_version = models.ForeignKey(AgentVersion, on_delete=models.SET_NULL, null=True, blank=True, related_name="improvement_candidates") class ReplayDataset(TimestampedModel): name = models.CharField(max_length=200) description = models.TextField(blank=True) version = models.PositiveIntegerField(default=1) status = models.CharField(max_length=32, default="DRAFT") selection_criteria = models.JSONField(default=dict, blank=True) frozen_at = models.DateTimeField(null=True, blank=True) metadata = models.JSONField(default=dict, blank=True) class Meta: constraints = [models.UniqueConstraint(fields=["name", "version"], name="unique_replay_dataset_version")] class ReplayCase(TimestampedModel): replay_dataset = models.ForeignKey(ReplayDataset, on_delete=models.CASCADE, related_name="cases") source_project = models.ForeignKey("projects.Project", on_delete=models.SET_NULL, null=True, blank=True, related_name="replay_cases") source_task = models.ForeignKey("projects.Task", on_delete=models.SET_NULL, null=True, blank=True, related_name="replay_cases") source_task_attempt = models.ForeignKey("projects.TaskAttempt", on_delete=models.SET_NULL, null=True, blank=True, related_name="replay_cases") source_graph_run = models.ForeignKey("graph.GraphRun", on_delete=models.SET_NULL, null=True, blank=True, related_name="replay_cases") task_type = models.CharField(max_length=80) project_type = models.CharField(max_length=100, blank=True) original_goal = models.TextField() acceptance_criteria = models.JSONField(default=list, blank=True) repository_path = models.TextField(blank=True) repository_baseline_ref = models.CharField(max_length=80) expected_evaluator_inputs = models.JSONField(default=dict, blank=True) failure_classification = models.CharField(max_length=120, blank=True) selection_metadata = models.JSONField(default=dict, blank=True) def save(self, *args: object, **kwargs: object) -> None: if self.replay_dataset.status == "FROZEN": raise ValidationError("Frozen replay dataset membership is immutable; create a new dataset version.") super().save(*args, **kwargs) class ProgenyExperiment(TimestampedModel): investigation = models.ForeignKey(ProgenyInvestigation, on_delete=models.SET_NULL, null=True, blank=True, related_name="experiments") improvement_candidate = models.ForeignKey(ImprovementCandidate, on_delete=models.SET_NULL, null=True, blank=True, related_name="experiments") target_type = models.CharField(max_length=80) target_identifier = models.CharField(max_length=240, blank=True) replay_dataset = models.ForeignKey(ReplayDataset, on_delete=models.PROTECT, related_name="experiments") hypothesis = models.TextField() success_criteria = models.JSONField(default=dict, blank=True) status = models.CharField(max_length=32, default="DRAFT") started_at = models.DateTimeField(null=True, blank=True) completed_at = models.DateTimeField(null=True, blank=True) metadata = models.JSONField(default=dict, blank=True) class ExperimentVariant(TimestampedModel): experiment = models.ForeignKey(ProgenyExperiment, on_delete=models.CASCADE, related_name="variants") role = models.CharField(max_length=32) target_type = models.CharField(max_length=80) target_reference = models.CharField(max_length=240) execution_graph_version = models.ForeignKey("graph.ExecutionGraphVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="experiment_variants") agent_version = models.ForeignKey(AgentVersion, on_delete=models.SET_NULL, null=True, blank=True, related_name="experiment_variants") configuration_snapshot = models.JSONField(default=dict) metadata = models.JSONField(default=dict, blank=True) def save(self, *args: object, **kwargs: object) -> None: if not self._state.adding: previous = ExperimentVariant.objects.get(pk=self.pk) if previous.configuration_snapshot != self.configuration_snapshot or previous.target_reference != self.target_reference: raise ValidationError("Experiment variant configuration is immutable; create a new variant.") super().save(*args, **kwargs) class ReplayRun(TimestampedModel): experiment = models.ForeignKey(ProgenyExperiment, on_delete=models.CASCADE, related_name="replay_runs") variant = models.ForeignKey(ExperimentVariant, on_delete=models.CASCADE, related_name="replay_runs") replay_case = models.ForeignKey(ReplayCase, on_delete=models.CASCADE, related_name="replay_runs") graph_run = models.ForeignKey("graph.GraphRun", on_delete=models.SET_NULL, null=True, blank=True, related_name="replay_runs") replay_task = models.ForeignKey("projects.Task", on_delete=models.SET_NULL, null=True, blank=True, related_name="replay_runs") started_at = models.DateTimeField(null=True, blank=True) completed_at = models.DateTimeField(null=True, blank=True) status = models.CharField(max_length=32, default="PENDING") failure_classification = models.CharField(max_length=80, blank=True) commit_candidate_sha = models.CharField(max_length=64, blank=True) telemetry = models.JSONField(default=dict, blank=True) failure_evidence = models.JSONField(default=dict, blank=True) metadata = models.JSONField(default=dict, blank=True) class ReplayResult(TimestampedModel): replay_run = models.OneToOneField(ReplayRun, on_delete=models.CASCADE, related_name="result") completion_status = models.CharField(max_length=32) tests_status = models.CharField(max_length=32, blank=True) reviewer_status = models.CharField(max_length=32, blank=True) judge_status = models.CharField(max_length=32, blank=True) accepted_candidate = models.BooleanField(default=False) metrics = models.JSONField(default=dict, blank=True) safety = models.JSONField(default=dict, blank=True) evidence = models.JSONField(default=dict, blank=True) class ExperimentComparison(TimestampedModel): experiment = models.OneToOneField(ProgenyExperiment, on_delete=models.CASCADE, related_name="comparison") champion_variant = models.ForeignKey(ExperimentVariant, on_delete=models.SET_NULL, null=True, blank=True, related_name="champion_comparisons") challenger_variant = models.ForeignKey(ExperimentVariant, on_delete=models.SET_NULL, null=True, blank=True, related_name="challenger_comparisons") aggregate_metrics = models.JSONField(default=dict, blank=True) paired_outcomes = models.JSONField(default=dict, blank=True) regression_cases = models.JSONField(default=list, blank=True) verdict = models.CharField(max_length=80, default="INCONCLUSIVE") reasons = models.JSONField(default=list, blank=True) approved_at = models.DateTimeField(null=True, blank=True) rejected_at = models.DateTimeField(null=True, blank=True) decided_by = models.CharField(max_length=120, blank=True)