diff --git a/agent/curator.py b/agent/curator.py index 619a3569ff..cf4b13f8ef 100644 --- a/agent/curator.py +++ b/agent/curator.py @@ -261,28 +261,33 @@ def apply_automatic_transitions(now: Optional[datetime] = None) -> Dict[str, int CURATOR_REVIEW_PROMPT = ( "You are running as Hermes' background skill CURATOR.\n\n" "Your job is to maintain the collection of AGENT-CREATED skills. Review " - "each one and decide what to do, using skill_manage and the curator tools.\n\n" + "each candidate below and decide what to do.\n\n" "Rules — all load-bearing, do not violate:\n" "1. You MUST NOT touch bundled or hub-installed skills. The candidate list " "below is already filtered to agent-created skills only.\n" - "2. You MUST NOT delete any skill. Archive (move to .archive/) is the " - "maximum action. Archives are recoverable; deletion is not.\n" - "3. You MUST NOT touch pinned skills. If a skill is pinned, skip it.\n" - "4. Prefer GENERALIZING overlapping skills by patching the better one and " - "archiving the duplicate, rather than leaving two narrow skills in the " + "2. You MUST NOT delete any skill. Archiving (moving the skill's directory " + "into ~/.hermes/skills/.archive/) is the maximum action. Archives are " + "recoverable; deletion is not.\n" + "3. You MUST NOT touch skills shown as pinned=yes. Skip them.\n" + "4. Prefer GENERALIZING overlapping skills by patching the stronger one " + "and archiving the weaker, rather than leaving two narrow skills in the " "collection.\n\n" - "For each candidate decide one of:\n" - " keep — leave as-is (most common default)\n" - " patch — fix stale commands, wrong paths, environment-specific " - "claims that are no longer true. Use skill_manage action=patch.\n" - " consolidate — when two skills overlap, patch the stronger one to " - "absorb the weaker, then archive the weaker via the 'archive_skill' tool.\n" - " archive — if the skill is genuinely obsolete and the sidecar shows " - "it has not been used recently. Use the 'archive_skill' tool.\n" - " pin — if the skill is rare but important (low use_count but " - "high value). Use the 'pin_skill' tool.\n\n" - "Start by calling skills_list and then skill_view on any skill you want to " - "consider patching or consolidating. Be conservative — if in doubt, keep. " + "Your toolset:\n" + " - skills_list, skill_view — read the current landscape\n" + " - skill_manage action=patch — fix stale commands, wrong paths, or " + "merge two overlapping skills by broadening the stronger one\n" + " - terminal — move a skill directory into the archive, " + "e.g. mv ~/.hermes/skills/ ~/.hermes/skills/.archive/\n\n" + "For each candidate, decide one of:\n" + " keep — leave as-is (most common default; don't over-curate)\n" + " patch — skill_manage action=patch to fix stale commands, wrong " + "paths, or env-specific claims that are no longer true\n" + " consolidate — two skills overlap: patch the stronger one to absorb " + "the weaker (skill_manage), then mv the weaker directory to .archive/\n" + " archive — the skill is genuinely obsolete and has not been used " + "recently: mv its directory to ~/.hermes/skills/.archive/\n\n" + "Start by calling skills_list and skill_view on anything you consider " + "patching or consolidating. Be conservative — if in doubt, keep. " "When you are done, write a one-sentence summary of what you changed." ) diff --git a/tests/agent/test_curator.py b/tests/agent/test_curator.py index a55094a85d..766a49e6d1 100644 --- a/tests/agent/test_curator.py +++ b/tests/agent/test_curator.py @@ -359,5 +359,37 @@ def test_curator_review_prompt_has_invariants(): assert "delete" in CURATOR_REVIEW_PROMPT.lower() assert "pinned" in CURATOR_REVIEW_PROMPT.lower() # Must mention the decisions the reviewer can make - for verb in ("keep", "patch", "archive", "pin"): + for verb in ("keep", "patch", "archive", "consolidate"): assert verb in CURATOR_REVIEW_PROMPT.lower() + + +def test_curator_review_prompt_points_at_existing_tools_only(): + """The review prompt must rely on existing tools (skill_manage + terminal) + and must NOT reference bespoke curator tools that are not registered + model tools.""" + from agent.curator import CURATOR_REVIEW_PROMPT + assert "skill_manage" in CURATOR_REVIEW_PROMPT + assert "skills_list" in CURATOR_REVIEW_PROMPT + assert "skill_view" in CURATOR_REVIEW_PROMPT + assert "terminal" in CURATOR_REVIEW_PROMPT.lower() + # These would be nice but aren't actually registered as tools — the + # curator uses skill_manage + terminal mv instead. + assert "archive_skill" not in CURATOR_REVIEW_PROMPT + assert "pin_skill" not in CURATOR_REVIEW_PROMPT + + +def test_curator_does_not_instruct_model_to_pin(): + """Pinning is a user opt-out, not a model decision. The prompt should + not tell the reviewer to pin skills autonomously.""" + from agent.curator import CURATOR_REVIEW_PROMPT + # "pinned" appears in the invariant ("skip pinned skills"), but "pin" + # as a decision verb should not. + lines = CURATOR_REVIEW_PROMPT.split("\n") + decision_block = "\n".join( + l for l in lines + if l.strip().startswith(("keep", "patch", "archive", "consolidate", "pin ")) + ) + # No standalone "pin" action line + assert not any(l.strip().startswith("pin ") for l in lines), ( + f"Found a pin action line in:\n{decision_block}" + )