fbcfbbced6
Check Cross-Plugin Imports / check (push) Has been cancelled
- New self_improvement plugin: models, services, routes, plugin - 4 SQLAlchemy models: ImprovementSignal, ImprovementPattern, ImprovementProposal, ImpactMeasurement - Migration 0132: 4 tables with RLS - Services: collect_signals, detect_patterns, create_proposal, evaluate_proposal, request_approval, activate_proposal, rollback_proposal, measure_impact - 11 API routes under /api/v1/improvement/ - Frontend: improvement.ts API client, ImprovementPanel.tsx in AISidebar proactive tab - 24/24 integration tests pass - Fix: __init__.py imports Plugin class for discover_builtins() (self_improvement + knowledge) - Fix: automation plugin register_plugin_contributions skips when no tenant exists
153 lines
8.2 KiB
Python
153 lines
8.2 KiB
Python
"""Self-improvement models — signals, patterns, proposals, impact measurements.
|
|
|
|
All models are real SQLAlchemy models with TenantMixin (no dataclasses).
|
|
Tables get RLS via migration.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import (
|
|
DateTime,
|
|
Float,
|
|
ForeignKey,
|
|
Index,
|
|
Integer,
|
|
String,
|
|
Text,
|
|
func,
|
|
)
|
|
from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.core.db import Base, TenantMixin
|
|
from app.models.owned_mixin import OwnedMixin
|
|
|
|
# Constants
|
|
SIGNAL_KINDS = ("failure", "retry", "dismissal", "correction", "observation", "handoff")
|
|
SEVERITY_LEVELS = ("info", "warning", "error")
|
|
PATTERN_KINDS = ("retry_bottleneck", "manual_correction", "suggestion_dismissal", "repetitive_handoff")
|
|
PATTERN_STATUSES = ("detected", "proposal_created", "resolved", "ignored")
|
|
PROPOSAL_STATUSES = (
|
|
"draft", "evaluating", "evaluated", "pending_approval",
|
|
"approved", "active", "rolled_back", "rejected", "expired",
|
|
)
|
|
PROPOSAL_TARGET_TYPES = ("agent", "skill", "trigger", "workflow", "miniapp_template", "plugin_patch")
|
|
|
|
|
|
class ImprovementSignal(Base, TenantMixin):
|
|
"""A single improvement signal collected from real system usage.
|
|
|
|
References source data (AgentRun, WorkflowInstance, ProactiveSuggestion,
|
|
AuditLog) by ID — stores no personal data copies.
|
|
"""
|
|
__tablename__ = "improvement_signals"
|
|
__table_args__ = (
|
|
Index("ix_impr_signals_tenant_kind", "tenant_id", "signal_kind"),
|
|
Index("ix_impr_signals_tenant_source", "tenant_id", "source_type"),
|
|
Index("ix_impr_signals_tenant_pattern", "tenant_id", "pattern_id"),
|
|
)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
source_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
source_ref_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True)
|
|
source_metadata: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
|
summary: Mapped[str] = mapped_column(Text, nullable=False)
|
|
signal_kind: Mapped[str] = mapped_column(String(30), nullable=False)
|
|
severity: Mapped[str] = mapped_column(String(20), nullable=False, default="info")
|
|
confidence: Mapped[float] = mapped_column(Float, nullable=False, default=0.5)
|
|
pattern_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
PGUUID(as_uuid=True), ForeignKey("improvement_patterns.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
|
|
|
|
|
class ImprovementPattern(Base, TenantMixin):
|
|
"""A detected pattern from grouped improvement signals."""
|
|
__tablename__ = "improvement_patterns"
|
|
__table_args__ = (
|
|
Index("ix_impr_patterns_tenant_status", "tenant_id", "status"),
|
|
Index("ix_impr_patterns_tenant_target", "tenant_id", "target_type"),
|
|
)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
pattern_kind: Mapped[str] = mapped_column(String(40), nullable=False)
|
|
title: Mapped[str] = mapped_column(String(300), nullable=False)
|
|
description: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
target_type: Mapped[str] = mapped_column(String(30), nullable=False)
|
|
target_name: Mapped[str] = mapped_column(String(200), nullable=False, default="unknown")
|
|
evidence_refs: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
|
|
occurrence_count: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
|
confidence: Mapped[float] = mapped_column(Float, nullable=False, default=0.5)
|
|
status: Mapped[str] = mapped_column(String(20), nullable=False, default="detected")
|
|
proposed_action: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now())
|
|
|
|
|
|
class ImprovementProposal(Base, TenantMixin, OwnedMixin):
|
|
"""A versioned improvement proposal with lifecycle.
|
|
|
|
Status flow:
|
|
draft -> evaluating -> evaluated -> pending_approval -> approved -> active
|
|
-> rolled_back
|
|
rejected <- pending_approval
|
|
expired <- pending_approval
|
|
"""
|
|
__tablename__ = "improvement_proposals"
|
|
__table_args__ = (
|
|
Index("ix_impr_proposals_tenant_status", "tenant_id", "status"),
|
|
Index("ix_impr_proposals_tenant_target", "tenant_id", "target_type"),
|
|
Index("ix_impr_proposals_tenant_pattern", "tenant_id", "pattern_id"),
|
|
)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
pattern_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
PGUUID(as_uuid=True), ForeignKey("improvement_patterns.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
title: Mapped[str] = mapped_column(String(300), nullable=False)
|
|
description: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
target_type: Mapped[str] = mapped_column(String(30), nullable=False)
|
|
target_ref_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True)
|
|
target_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
|
version_number: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
|
proposed_config: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
|
previous_config: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
|
evidence_refs: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
|
|
rationale: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
expected_benefit: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
risk_assessment: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
evaluation_result: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
|
evaluated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
approval_request_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True)
|
|
approved_by: Mapped[uuid.UUID | None] = mapped_column(
|
|
PGUUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
approved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
activated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
rolled_back_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
rollback_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[str] = mapped_column(String(20), nullable=False, default="draft")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now())
|
|
|
|
|
|
class ImpactMeasurement(Base, TenantMixin):
|
|
"""Pre/post impact measurement for an activated proposal."""
|
|
__tablename__ = "improvement_impact_measurements"
|
|
__table_args__ = (
|
|
Index("ix_impr_impact_tenant_proposal", "tenant_id", "proposal_id"),
|
|
)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
proposal_id: Mapped[uuid.UUID] = mapped_column(
|
|
PGUUID(as_uuid=True), ForeignKey("improvement_proposals.id", ondelete="CASCADE"), nullable=False
|
|
)
|
|
pre_metrics: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
|
post_metrics: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
|
delta: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
|
assessment: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
is_positive: Mapped[str] = mapped_column(String(20), nullable=False, default="neutral")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|