File size: 8,106 Bytes
92c4ae6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
"""
Auto-Dev Capability Gate

Gates Auto-Dev features based on agent maturity level (graduation framework)
and workspace settings. This ensures:

1. Only agents that have proven competence can use self-evolution features
2. Users must explicitly enable Auto-Dev in workspace settings
3. Users are notified when agents graduate into new capabilities

Maturity requirements:
    - auto_dev.memento_skills    → INTERN    (skill suggestions, user approval required)
    - auto_dev.alpha_evolver     → SUPERVISED (mutation loops, results queued for review)
    - auto_dev.background_evolution → AUTONOMOUS (background loops, sandbox-gated)
"""

import logging
from typing import Any

from sqlalchemy.orm import Session

logger = logging.getLogger(__name__)


# Maturity level constants (mirrors CapabilityMaturityLevel from graduation service)
STUDENT = "student"
INTERN = "intern"
SUPERVISED = "supervised"
AUTONOMOUS = "autonomous"

MATURITY_ORDER = [STUDENT, INTERN, SUPERVISED, AUTONOMOUS]


def is_at_least(current: str, required: str) -> bool:
    """Check if current maturity level meets or exceeds the required level."""
    if current not in MATURITY_ORDER or required not in MATURITY_ORDER:
        return False
    return MATURITY_ORDER.index(current) >= MATURITY_ORDER.index(required)


class AutoDevCapabilityService:
    """Gates Auto-Dev features based on agent maturity and workspace settings."""

    # Maturity requirements for each Auto-Dev capability
    CAPABILITY_GATES: dict[str, str] = {
        "auto_dev.memento_skills": INTERN,
        "auto_dev.alpha_evolver": SUPERVISED,
        "auto_dev.background_evolution": AUTONOMOUS,
    }

    def __init__(self, db: Session):
        self.db = db
        self._graduation_service = None

    @property
    def graduation(self):
        """Lazy-load graduation service to avoid circular imports."""
        if self._graduation_service is None:
            try:
                from core.capability_graduation_service import (
                    CapabilityGraduationService,
                )

                self._graduation_service = CapabilityGraduationService(self.db)
            except ImportError:
                logger.warning(
                    "CapabilityGraduationService not available — "
                    "Auto-Dev capabilities will default to STUDENT (disabled)"
                )
        return self._graduation_service

    def can_use(
        self,
        agent_id: str,
        capability: str,
        workspace_settings: dict[str, Any] | None = None,
    ) -> bool:
        """
        Check if an agent can use a specific Auto-Dev capability.

        Requires BOTH:
        1. Workspace settings allow it (auto_dev.enabled + per-capability toggle)
        2. Agent has graduated to the required maturity level

        Args:
            agent_id: The agent to check
            capability: e.g., "auto_dev.memento_skills"
            workspace_settings: Workspace.configuration dict (or Tenant.configuration)

        Returns:
            True if the agent is allowed to use this capability
        """
        settings = workspace_settings or {}

        # 1. Check workspace-level toggle
        auto_dev_settings = settings.get("auto_dev", {})
        if not auto_dev_settings.get("enabled", False):
            return False

        # 2. Check capability-specific toggle
        cap_key = capability.split(".")[-1]  # e.g., "memento_skills"
        if not auto_dev_settings.get(cap_key, True):
            return False

        # 3. Check graduation maturity gate
        required = self.CAPABILITY_GATES.get(capability)
        if not required:
            logger.warning(f"Unknown Auto-Dev capability: {capability}")
            return False

        current = self._get_agent_maturity(agent_id, capability)
        return is_at_least(current, required)

    def record_usage(self, agent_id: str, capability: str, success: bool) -> None:
        """
        Record Auto-Dev usage to progress agent maturity via graduation framework.

        Each successful use contributes to the agent's graduation score for
        this capability, eventually unlocking higher tiers.
        """
        if self.graduation:
            try:
                self.graduation.record_usage(agent_id, capability, success)
            except Exception as e:
                logger.error(f"Failed to record Auto-Dev usage: {e}")

    def check_daily_limits(
        self,
        agent_id: str,
        capability: str,
        workspace_settings: dict[str, Any] | None = None,
    ) -> bool:
        """
        Check if the agent has exceeded daily Auto-Dev limits.

        Limits are defined in workspace settings:
        - max_mutations_per_day (default: 10)
        - max_skill_candidates_per_day (default: 5)
        """
        settings = (workspace_settings or {}).get("auto_dev", {})

        if capability == "auto_dev.alpha_evolver":
            max_daily = settings.get("max_mutations_per_day", 10)
        elif capability == "auto_dev.memento_skills":
            max_daily = settings.get("max_skill_candidates_per_day", 5)
        else:
            return True

        # Count today's usage from models
        from datetime import datetime, timezone

        today_start = datetime.now(timezone.utc).replace(
            hour=0, minute=0, second=0, microsecond=0
        )

        try:
            if capability == "auto_dev.alpha_evolver":
                from core.auto_dev.models import ToolMutation

                count = (
                    self.db.query(ToolMutation)
                    .filter(
                        ToolMutation.tenant_id == self._get_agent_tenant(agent_id),
                        ToolMutation.created_at >= today_start,
                    )
                    .count()
                )
            elif capability == "auto_dev.memento_skills":
                from core.auto_dev.models import SkillCandidate

                count = (
                    self.db.query(SkillCandidate)
                    .filter(
                        SkillCandidate.agent_id == agent_id,
                        SkillCandidate.created_at >= today_start,
                    )
                    .count()
                )
            else:
                return True

            return count < max_daily
        except Exception as e:
            logger.error(f"Failed to check daily limits: {e}")
            return True  # Fail open to avoid blocking the system

    def notify_capability_unlocked(
        self, agent_id: str, capability: str
    ) -> dict[str, Any]:
        """
        Generate notification payload when an agent graduates into a new capability.

        The caller (e.g., GraduationService) should deliver this notification
        to the user via the appropriate channel.
        """
        cap_display = capability.replace("auto_dev.", "").replace("_", " ").title()
        return {
            "type": "auto_dev_capability_unlocked",
            "agent_id": agent_id,
            "capability": capability,
            "message": (
                f"Agent has graduated to use {cap_display}. "
                f"Enable it in Settings > Auto-Dev to activate."
            ),
            "action_required": True,
        }

    def _get_agent_maturity(self, agent_id: str, capability: str) -> str:
        """Get agent's current maturity for a capability."""
        if self.graduation:
            try:
                return self.graduation.get_maturity(agent_id, capability)
            except Exception as e:
                logger.error(f"Failed to get maturity for {agent_id}: {e}")
        return STUDENT

    def _get_agent_tenant(self, agent_id: str) -> str | None:
        """Get tenant_id for an agent."""
        try:
            from core.models import AgentRegistry

            agent = (
                self.db.query(AgentRegistry)
                .filter(AgentRegistry.id == agent_id)
                .first()
            )
            return agent.tenant_id if agent else None
        except Exception:
            return None