56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
"""Procedural memory: reusable skills/workflows for AGI."""
|
|
|
|
from typing import Any
|
|
|
|
from fusionagi.schemas.skill import Skill
|
|
from fusionagi._logger import logger
|
|
|
|
|
|
class ProceduralMemory:
|
|
"""
|
|
Skill store: reusable workflows and procedures. Invokable by name;
|
|
write/update rules enforced by caller.
|
|
"""
|
|
|
|
def __init__(self, max_skills: int = 5000) -> None:
|
|
self._skills: dict[str, Skill] = {}
|
|
self._by_name: dict[str, str] = {} # name -> skill_id (latest)
|
|
self._max_skills = max_skills
|
|
|
|
def add_skill(self, skill: Skill) -> None:
|
|
"""Register a skill (overwrites same skill_id)."""
|
|
if len(self._skills) >= self._max_skills and skill.skill_id not in self._skills:
|
|
self._evict_one()
|
|
self._skills[skill.skill_id] = skill
|
|
self._by_name[skill.name] = skill.skill_id
|
|
logger.debug("Procedural memory: skill added", extra={"skill_id": skill.skill_id, "name": skill.name})
|
|
|
|
def get_skill(self, skill_id: str) -> Skill | None:
|
|
"""Return skill by id or None."""
|
|
return self._skills.get(skill_id)
|
|
|
|
def get_skill_by_name(self, name: str) -> Skill | None:
|
|
"""Return latest skill with this name or None."""
|
|
sid = self._by_name.get(name)
|
|
return self._skills.get(sid) if sid else None
|
|
|
|
def list_skills(self, limit: int = 200) -> list[Skill]:
|
|
"""Return skills (e.g. for planner)."""
|
|
return list(self._skills.values())[-limit:]
|
|
|
|
def remove_skill(self, skill_id: str) -> bool:
|
|
"""Remove skill. Returns True if existed."""
|
|
if skill_id not in self._skills:
|
|
return False
|
|
name = self._skills[skill_id].name
|
|
del self._skills[skill_id]
|
|
if self._by_name.get(name) == skill_id:
|
|
del self._by_name[name]
|
|
return True
|
|
|
|
def _evict_one(self) -> None:
|
|
if not self._skills:
|
|
return
|
|
rid = next(iter(self._skills))
|
|
self.remove_skill(rid)
|