annator-command-center / core /auto_dev /capability_gate.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 3)
92c4ae6 verified
Raw
History Blame Contribute Delete
8.11 kB
"""
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