"""Small Skill Registry for AI agents. Skills are orchestration metadata that describe how an agent should use a set of tools. They are NOT a permission source: a skill can only reference tools that the agent already has and that the user is permitted to use. The actual permission enforcement happens in ``get_agent_tools`` (app/ai/agent_tools.py). """ from __future__ import annotations from dataclasses import dataclass, field from typing import Any @dataclass class SkillDefinition: """A skill definition — orchestration metadata for a set of tools.""" name: str description: str instructions: str # How to use this skill allowed_tool_ids: list[str] = field(default_factory=list) # Tool IDs this skill can use context_policy: dict[str, Any] | None = None # Optional context inclusion rules category: str = "general" def to_dict(self) -> dict[str, Any]: """Serialize to a plain dict for API responses.""" return { "name": self.name, "description": self.description, "instructions": self.instructions, "allowed_tool_ids": list(self.allowed_tool_ids or []), "context_policy": self.context_policy, "category": self.category, } class SkillRegistry: """Registry for skill definitions. Skills are orchestration metadata, NOT a permission source. """ _instance: SkillRegistry | None = None def __new__(cls) -> SkillRegistry: if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._skills: dict[str, SkillDefinition] = {} return cls._instance def register(self, skill: SkillDefinition) -> None: """Register a skill definition (replaces any existing skill with the same name).""" self._skills[skill.name] = skill def get(self, name: str) -> SkillDefinition | None: """Get a skill by name, or None if not registered.""" return self._skills.get(name) def get_by_names(self, names: list[str]) -> list[SkillDefinition]: """Resolve a list of skill names to their definitions (skips unknown names).""" return [self._skills[name] for name in names if name in self._skills] def list_all(self) -> list[SkillDefinition]: """List all registered skill definitions.""" return list(self._skills.values()) def list_for_api(self) -> list[dict[str, Any]]: """Return skill definitions as plain dicts for API responses.""" return [skill.to_dict() for skill in self._skills.values()] def unregister(self, name: str) -> None: """Remove a skill definition by name.""" self._skills.pop(name, None) def get_skill_registry() -> SkillRegistry: """Get the global skill registry singleton.""" return SkillRegistry() __all__ = ["SkillDefinition", "SkillRegistry", "get_skill_registry"]