Files
cleveragents-core/benchmarks/skill_add_persist_bench.py
T
freemo 9704281bdd
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 19s
CI / build (pull_request) Successful in 19s
CI / security (pull_request) Successful in 36s
CI / typecheck (pull_request) Successful in 39s
CI / unit_tests (pull_request) Successful in 2m29s
CI / docker (pull_request) Successful in 41s
CI / integration_tests (pull_request) Successful in 3m14s
CI / coverage (pull_request) Successful in 4m26s
CI / benchmark-regression (pull_request) Successful in 30m9s
CI / lint (push) Successful in 14s
CI / build (push) Successful in 16s
CI / quality (push) Successful in 17s
CI / security (push) Successful in 33s
CI / typecheck (push) Successful in 34s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m18s
CI / docker (push) Successful in 39s
CI / integration_tests (push) Successful in 3m7s
CI / coverage (push) Successful in 4m29s
CI / benchmark-publish (push) Has been cancelled
fix(skill): persist skill registration to database after add
Wire SkillService to use SkillRepository for database persistence,
fixing the bug where `agents skill add` stored skills only in an
in-memory OrderedDict that was lost when the CLI process exited.

Changes:
- SkillService now accepts optional skill_repo and session_factory
  parameters.  When provided, add_skill() persists to the database
  and the constructor pre-loads existing skills from DB rows.
- _get_skill_service() in the CLI now creates a DB-backed service
  following the same engine/sessionmaker/repository pattern used by
  the tool CLI (tool.py).
- _reset_skill_service() now installs a fresh in-memory SkillService
  (instead of setting None) to avoid DB side-effects during unit
  testing with parallel runners.
- remove_skill() also persists the deletion to the database.

Test coverage:
- Behave BDD: features/skill_add_persist.feature (4 scenarios)
- Robot Framework: robot/skill_add_persist.robot (3 smoke tests)
- ASV benchmark: benchmarks/skill_add_persist_bench.py

ISSUES CLOSED: #620
2026-03-08 02:57:44 +00:00

128 lines
3.7 KiB
Python

"""ASV benchmarks for skill add persistence (issue #620).
Measures the performance of:
- Adding a skill with DB persistence
- Listing skills after DB-backed add
- Removing a skill with DB persistence
"""
from __future__ import annotations
import importlib
import os
import sys
import tempfile
from pathlib import Path
from typing import Any
# Ensure the local *source* tree is importable even when ASV has an
# older build of the package installed.
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
import cleveragents # noqa: E402
importlib.reload(cleveragents)
from sqlalchemy import create_engine, event # noqa: E402
from sqlalchemy.orm import Session, sessionmaker # noqa: E402
from cleveragents.application.services.skill_service import ( # noqa: E402
SkillService,
)
from cleveragents.infrastructure.database.models import Base # noqa: E402
from cleveragents.infrastructure.database.repositories import ( # noqa: E402
SkillRepository,
)
from cleveragents.skills.schema import SkillConfigSchema # noqa: E402
_VALID_YAML = """\
name: local/bench-persist
description: "Benchmark persistent skill"
tools:
- name: builtin/read_file
- name: builtin/write_file
"""
def _enable_fk(dbapi_conn: Any, _rec: Any) -> None:
cursor = dbapi_conn.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
def _make_service(factory: Any) -> SkillService:
shared = factory()
def _sf() -> Session:
return shared
repo = SkillRepository(session_factory=_sf)
return SkillService(skill_repo=repo, session_factory=_sf)
class SkillAddPersistSuite:
"""Benchmark skill add with DB persistence."""
timeout = 60.0
def setup(self) -> None:
self.engine = create_engine("sqlite:///:memory:", echo=False)
event.listen(self.engine, "connect", _enable_fk)
Base.metadata.create_all(self.engine)
self.factory: sessionmaker[Session] = sessionmaker(
bind=self.engine,
)
fd, self._path = tempfile.mkstemp(suffix=".yaml")
with os.fdopen(fd, "w") as fh:
fh.write(_VALID_YAML)
def teardown(self) -> None:
Path(self._path).unlink(missing_ok=True)
self.engine.dispose()
def time_add_persist(self) -> None:
"""Benchmark add with persistence."""
svc = _make_service(self.factory)
config = SkillConfigSchema.from_yaml_file(self._path)
svc.add_skill(config, config_path=self._path)
class SkillListAfterPersistSuite:
"""Benchmark list after persistent adds."""
timeout = 60.0
def setup(self) -> None:
self.engine = create_engine("sqlite:///:memory:", echo=False)
event.listen(self.engine, "connect", _enable_fk)
Base.metadata.create_all(self.engine)
self.factory: sessionmaker[Session] = sessionmaker(
bind=self.engine,
)
svc = _make_service(self.factory)
for i in range(20):
yaml_text = (
f"name: local/bench-list-{i}\n"
f'description: "Bench {i}"\n'
"tools:\n"
" - name: builtin/read_file\n"
)
fd, path = tempfile.mkstemp(suffix=".yaml")
with os.fdopen(fd, "w") as fh:
fh.write(yaml_text)
config = SkillConfigSchema.from_yaml_file(path)
svc.add_skill(config, config_path=path)
Path(path).unlink(missing_ok=True)
def teardown(self) -> None:
self.engine.dispose()
def time_list_persisted_skills(self) -> None:
"""Benchmark listing skills from a fresh service."""
svc = _make_service(self.factory)
svc.list_skills()