324 lines
18 KiB
Python
324 lines
18 KiB
Python
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)
|