mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
fix: curator labels bundled skills as agent-created (#64393)
This commit is contained in:
parent
b6accee0d7
commit
b9fedab47a
6 changed files with 56 additions and 31 deletions
|
|
@ -325,7 +325,7 @@ def apply_automatic_transitions(now: Optional[datetime] = None) -> Dict[str, int
|
|||
|
||||
counts = {"marked_stale": 0, "archived": 0, "reactivated": 0, "checked": 0, "seeded": 0}
|
||||
|
||||
for row in _u.agent_created_report():
|
||||
for row in _u.curated_report():
|
||||
counts["checked"] += 1
|
||||
name = row["name"]
|
||||
if row.get("pinned"):
|
||||
|
|
@ -1472,15 +1472,16 @@ def _render_report_markdown(p: Dict[str, Any]) -> str:
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _render_candidate_list() -> str:
|
||||
"""Human/agent-readable list of agent-created skills with usage stats."""
|
||||
rows = skill_usage.agent_created_report()
|
||||
"""Human/agent-readable list of curator-managed skills with usage stats."""
|
||||
rows = skill_usage.curated_report()
|
||||
if not rows:
|
||||
return "No agent-created skills to review."
|
||||
return "No curator-managed skills to review."
|
||||
cron_referenced = _cron_referenced_skills()
|
||||
lines = [f"Agent-created skills ({len(rows)}):\n"]
|
||||
lines = [f"Curator-managed skills ({len(rows)}):\n"]
|
||||
for r in rows:
|
||||
lines.append(
|
||||
f"- {r['name']} "
|
||||
f"provenance={r.get('provenance', 'agent')} "
|
||||
f"state={r['state']} "
|
||||
f"pinned={'yes' if r.get('pinned') else 'no'} "
|
||||
f"cron={'yes' if r['name'] in cron_referenced else 'no'} "
|
||||
|
|
@ -1533,7 +1534,7 @@ def run_curator_review(
|
|||
if dry_run:
|
||||
# Count candidates without mutating state.
|
||||
try:
|
||||
report = skill_usage.agent_created_report()
|
||||
report = skill_usage.curated_report()
|
||||
counts = {
|
||||
"checked": len(report),
|
||||
"marked_stale": 0,
|
||||
|
|
@ -1586,7 +1587,7 @@ def run_curator_review(
|
|||
nonlocal auto_summary
|
||||
# Snapshot skill state BEFORE the LLM pass so the report can diff.
|
||||
try:
|
||||
before_report = skill_usage.agent_created_report()
|
||||
before_report = skill_usage.curated_report()
|
||||
except Exception:
|
||||
before_report = []
|
||||
before_names = {r.get("name") for r in before_report if isinstance(r, dict)}
|
||||
|
|
@ -1612,7 +1613,7 @@ def run_curator_review(
|
|||
state2["last_run_duration_seconds"] = elapsed
|
||||
state2["last_run_summary"] = final_summary
|
||||
try:
|
||||
after_report = skill_usage.agent_created_report()
|
||||
after_report = skill_usage.curated_report()
|
||||
except Exception:
|
||||
after_report = []
|
||||
try:
|
||||
|
|
@ -1699,7 +1700,7 @@ def run_curator_review(
|
|||
try:
|
||||
rename_lines = _build_rename_summary(
|
||||
before_names=before_names,
|
||||
after_report=skill_usage.agent_created_report(),
|
||||
after_report=skill_usage.curated_report(),
|
||||
tool_calls=llm_meta.get("tool_calls", []) or [],
|
||||
model_final=llm_meta.get("final", "") or "",
|
||||
)
|
||||
|
|
@ -1717,7 +1718,7 @@ def run_curator_review(
|
|||
# reporting bug never breaks the curator itself. Report path is
|
||||
# recorded in state so `hermes curator status` can point at it.
|
||||
try:
|
||||
after_report = skill_usage.agent_created_report()
|
||||
after_report = skill_usage.curated_report()
|
||||
except Exception:
|
||||
after_report = []
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -82,20 +82,28 @@ def _cmd_status(args) -> int:
|
|||
f"{'' if curator.get_consolidate() else ' (prune-only; LLM merge pass opt-in)'}"
|
||||
)
|
||||
|
||||
rows = skill_usage.agent_created_report()
|
||||
rows = skill_usage.curated_report()
|
||||
if not rows:
|
||||
print("\nno agent-created skills")
|
||||
print("\nno curator-managed skills")
|
||||
return 0
|
||||
|
||||
by_state = {"active": [], "stale": [], "archived": []}
|
||||
pinned = []
|
||||
agent_count = 0
|
||||
bundled_count = 0
|
||||
for r in rows:
|
||||
state_name = r.get("state", "active")
|
||||
by_state.setdefault(state_name, []).append(r)
|
||||
if r.get("pinned"):
|
||||
pinned.append(r["name"])
|
||||
prov = r.get("provenance", "agent")
|
||||
if prov == "agent":
|
||||
agent_count += 1
|
||||
elif prov == "bundled":
|
||||
bundled_count += 1
|
||||
|
||||
print(f"\nagent-created skills: {len(rows)} total")
|
||||
print(f"\ncurator-managed skills: {len(rows)} total "
|
||||
f"(agent-created={agent_count} bundled={bundled_count})")
|
||||
for state_name in ("active", "stale", "archived"):
|
||||
bucket = by_state.get(state_name, [])
|
||||
print(f" {state_name:10s} {len(bucket)}")
|
||||
|
|
@ -317,7 +325,7 @@ def _idle_days(record: dict) -> Optional[int]:
|
|||
|
||||
|
||||
def _cmd_prune(args) -> int:
|
||||
"""Bulk-archive agent-created skills idle for >= N days.
|
||||
"""Bulk-archive curator-managed skills idle for >= N days.
|
||||
|
||||
Pinned skills are exempt. Already-archived skills are skipped. Default
|
||||
``--days 90`` matches a conservative read of the curator's own archive
|
||||
|
|
@ -333,7 +341,7 @@ def _cmd_prune(args) -> int:
|
|||
skip_confirm = bool(getattr(args, "yes", False))
|
||||
|
||||
candidates = []
|
||||
for r in skill_usage.agent_created_report():
|
||||
for r in skill_usage.curated_report():
|
||||
if r.get("pinned"):
|
||||
continue
|
||||
if r.get("state") == skill_usage.STATE_ARCHIVED:
|
||||
|
|
@ -491,7 +499,7 @@ def _cmd_list_archived(args) -> int:
|
|||
def _cmd_usage(args) -> int:
|
||||
"""Show usage telemetry for ALL skills, with provenance.
|
||||
|
||||
Unlike `status` (curator-scoped to agent-created candidates), this lists
|
||||
Unlike `status` (curator-scoped to curated candidates), this lists
|
||||
every skill on disk — bundled built-ins and hub-installed included — so you
|
||||
can see how often each is actually used regardless of curation.
|
||||
"""
|
||||
|
|
@ -635,7 +643,7 @@ def register_cli(parent: argparse.ArgumentParser) -> None:
|
|||
|
||||
p_prune = subs.add_parser(
|
||||
"prune",
|
||||
help="Bulk-archive agent-created skills idle for >= N days (default 90)",
|
||||
help="Bulk-archive curator-managed skills idle for >= N days (default 90)",
|
||||
)
|
||||
p_prune.add_argument(
|
||||
"--days", type=int, default=90,
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ def test_prune_nothing_to_do(monkeypatch, capsys):
|
|||
import hermes_cli.curator as curator_cli
|
||||
import tools.skill_usage as skill_usage
|
||||
|
||||
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: [])
|
||||
monkeypatch.setattr(skill_usage, "curated_report", lambda: [])
|
||||
rc = curator_cli._cmd_prune(_ns(days=30, yes=True, dry_run=False))
|
||||
assert rc == 0
|
||||
assert "nothing to prune" in capsys.readouterr().out
|
||||
|
|
@ -117,7 +117,7 @@ def test_prune_filters_pinned_and_archived(monkeypatch, capsys):
|
|||
_mk_record("recent", idle_days=10),
|
||||
_mk_record("old-active", idle_days=200),
|
||||
]
|
||||
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: rows)
|
||||
monkeypatch.setattr(skill_usage, "curated_report", lambda: rows)
|
||||
archived = []
|
||||
monkeypatch.setattr(
|
||||
skill_usage, "archive_skill",
|
||||
|
|
@ -144,7 +144,7 @@ def test_prune_falls_back_to_created_at_when_never_used(monkeypatch, capsys):
|
|||
# Force last_activity_at to None explicitly
|
||||
rows[0]["last_activity_at"] = None
|
||||
|
||||
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: rows)
|
||||
monkeypatch.setattr(skill_usage, "curated_report", lambda: rows)
|
||||
archived = []
|
||||
monkeypatch.setattr(
|
||||
skill_usage, "archive_skill",
|
||||
|
|
@ -160,7 +160,7 @@ def test_prune_dry_run_makes_no_changes(monkeypatch, capsys):
|
|||
import tools.skill_usage as skill_usage
|
||||
|
||||
rows = [_mk_record("old-skill", idle_days=200)]
|
||||
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: rows)
|
||||
monkeypatch.setattr(skill_usage, "curated_report", lambda: rows)
|
||||
archived = []
|
||||
monkeypatch.setattr(
|
||||
skill_usage, "archive_skill",
|
||||
|
|
@ -179,7 +179,7 @@ def test_prune_prompts_without_yes(monkeypatch, capsys):
|
|||
import tools.skill_usage as skill_usage
|
||||
|
||||
rows = [_mk_record("old-skill", idle_days=200)]
|
||||
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: rows)
|
||||
monkeypatch.setattr(skill_usage, "curated_report", lambda: rows)
|
||||
archived = []
|
||||
monkeypatch.setattr(
|
||||
skill_usage, "archive_skill",
|
||||
|
|
@ -197,7 +197,7 @@ def test_prune_confirms_with_y(monkeypatch, capsys):
|
|||
import tools.skill_usage as skill_usage
|
||||
|
||||
rows = [_mk_record("old-skill", idle_days=200)]
|
||||
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: rows)
|
||||
monkeypatch.setattr(skill_usage, "curated_report", lambda: rows)
|
||||
archived = []
|
||||
monkeypatch.setattr(
|
||||
skill_usage, "archive_skill",
|
||||
|
|
@ -217,7 +217,7 @@ def test_prune_reports_partial_failure(monkeypatch, capsys):
|
|||
_mk_record("ok-skill", idle_days=200),
|
||||
_mk_record("bad-skill", idle_days=200),
|
||||
]
|
||||
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: rows)
|
||||
monkeypatch.setattr(skill_usage, "curated_report", lambda: rows)
|
||||
|
||||
def fake_archive(name):
|
||||
if name == "bad-skill":
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ def test_status_uses_last_activity_not_only_last_used(monkeypatch, capsys):
|
|||
monkeypatch.setattr(curator_state, "get_interval_hours", lambda: 168)
|
||||
monkeypatch.setattr(curator_state, "get_stale_after_days", lambda: 30)
|
||||
monkeypatch.setattr(curator_state, "get_archive_after_days", lambda: 90)
|
||||
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: [
|
||||
monkeypatch.setattr(skill_usage, "curated_report", lambda: [
|
||||
{
|
||||
"name": "recently-viewed",
|
||||
"state": "active",
|
||||
|
|
@ -171,7 +171,7 @@ def test_status_hides_most_active_when_all_zero(curator_status_env):
|
|||
def test_status_no_skills_produces_clean_empty_output(curator_status_env):
|
||||
env = curator_status_env
|
||||
out = _capture_status(env["curator_cli"])
|
||||
assert "no agent-created skills" in out
|
||||
assert "no curator-managed skills" in out
|
||||
# None of the ranking sections render
|
||||
assert "most active" not in out
|
||||
assert "least active" not in out
|
||||
|
|
@ -194,7 +194,7 @@ def test_status_marks_missing_last_report_path(monkeypatch, capsys, tmp_path):
|
|||
monkeypatch.setattr(curator_state, "get_interval_hours", lambda: 168)
|
||||
monkeypatch.setattr(curator_state, "get_stale_after_days", lambda: 30)
|
||||
monkeypatch.setattr(curator_state, "get_archive_after_days", lambda: 90)
|
||||
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: [])
|
||||
monkeypatch.setattr(skill_usage, "curated_report", lambda: [])
|
||||
|
||||
assert curator_cli._cmd_status(SimpleNamespace()) == 0
|
||||
|
||||
|
|
|
|||
|
|
@ -786,7 +786,7 @@ def test_end_to_end_telemetry_tracked_but_lifecycle_refused(skills_home):
|
|||
|
||||
def test_usage_report_covers_all_provenance(skills_home):
|
||||
"""usage_report() surfaces every skill with provenance, unlike the
|
||||
curator-scoped agent_created_report()."""
|
||||
curator-scoped curated_report()."""
|
||||
from tools.skill_usage import (
|
||||
bump_use, usage_report, mark_agent_created,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -875,11 +875,15 @@ def _find_external_skill_dir(skill_name: str) -> Optional[Path]:
|
|||
# Reporting — for the curator CLI / slash command
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def agent_created_report() -> List[Dict[str, Any]]:
|
||||
"""Return a list of {name, state, pinned, last_activity_at, ...}
|
||||
def curated_report() -> List[Dict[str, Any]]:
|
||||
"""Return a list of {name, provenance, state, pinned, last_activity_at, ...}
|
||||
records for every curator-managed skill. Missing usage records are
|
||||
backfilled with defaults so callers can always index fields.
|
||||
|
||||
``provenance`` is 'agent', 'bundled', or 'hub' (see :func:`provenance`).
|
||||
Bundled skills are only included when ``curator.prune_builtins`` is enabled.
|
||||
Hub-installed skills are never included.
|
||||
|
||||
Each row carries ``_persisted``: True when a real record exists in
|
||||
``.usage.json``, False when the row is a fresh backfill (e.g. a built-in
|
||||
seen for the first time). The curator uses this to seed the inactivity
|
||||
|
|
@ -897,10 +901,22 @@ def agent_created_report() -> List[Dict[str, Any]]:
|
|||
row = {"name": name, **rec, "_persisted": persisted}
|
||||
row["last_activity_at"] = latest_activity_at(row)
|
||||
row["activity_count"] = activity_count(row)
|
||||
row["provenance"] = provenance(name)
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
|
||||
def agent_created_report() -> List[Dict[str, Any]]:
|
||||
"""DEPRECATED — use :func:`curated_report` instead.
|
||||
|
||||
Used to return everything :func:`curated_report` returns (including bundled
|
||||
skills when ``curator.prune_builtins`` is enabled), which made the
|
||||
"agent-created" name misleading. Kept as a compatibility alias for
|
||||
external callers; new code should call ``curated_report()``.
|
||||
"""
|
||||
return curated_report()
|
||||
|
||||
|
||||
def provenance(skill_name: str) -> str:
|
||||
"""Classify a skill's origin: 'hub', 'bundled', or 'agent'.
|
||||
|
||||
|
|
@ -917,7 +933,7 @@ def provenance(skill_name: str) -> str:
|
|||
def usage_report() -> List[Dict[str, Any]]:
|
||||
"""Return usage telemetry for EVERY skill on disk, with provenance.
|
||||
|
||||
Unlike ``agent_created_report()`` (which is scoped to curator-managed
|
||||
Unlike ``curated_report()`` (which is scoped to curator-managed
|
||||
candidates), this surfaces all skills — bundled built-ins and
|
||||
hub-installed included — so callers can answer "how often is this skill
|
||||
used" independent of whether it's ever curated. Rows carry a
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue