feat(curator): umbrella-first prompt, inherit parent config, unbounded iterations
Based on three live test runs against 346 agent-created skills on the author's own setup (~6.5 min, opus-4.7, 86 API calls), the curator prompt needed three sharpenings before it consistently produced real umbrella consolidation instead of passive audit output: **Umbrella-first framing.** The original 'decide keep/patch/archive/ consolidate' framing lets opus default to 'keep' whenever two skills aren't byte-identical. The new prompt explicitly tells the reviewer that pairwise distinctness is the wrong bar — the right question is 'would a human maintainer write this as N separate skills, or one skill with N labeled subsections?' Expect 10-25 prefix clusters; merge each into an umbrella via one of three methods. **Three concrete consolidation methods.** (a) Merge into an existing umbrella (patch the broadest skill, archive siblings); (b) Create a new umbrella SKILL.md (skill_manage action=create); (c) Demote session-specific detail into references/, templates/, or scripts/ under the umbrella via skill_manage action=write_file, then archive the narrow sibling. This matches the support-file vocabulary the review-prompt side already uses (PR #17213). **Two observed bailouts pre-empted:** 'usage counters are zero so I can't judge' (rule 4: judge on content, not use_count) and 'each has a distinct trigger' (rule 5: pairwise distinctness is the wrong bar). **Config-aware parent inheritance.** _run_llm_review() was building AIAgent() without explicit provider/model, hitting an auto-resolve path that returned empty credentials → HTTP 400 'No models provided' against OpenRouter. Fork now inherits the user's main provider and model (via load_config + resolve_runtime_provider) before spawning — runs on whatever the user is currently on, OAuth-backed or pool-backed included. **Unbounded iteration ceiling.** max_iterations=8 was way too low for an umbrella-build pass over hundreds of skills. A live pass takes 50-100 API calls (scanning, clustering, skill_view'ing candidates, patching umbrellas, mv'ing siblings). Raised to 9999 — the natural stopping criterion is 'no more clusters worth processing', not an arbitrary tool-call budget. **Tests updated:** test_curator_review_prompt_has_invariants accepts DO NOT / MUST NOT and drops 'keep' from the required-verb set (the umbrella-first prompt correctly deemphasizes 'keep' as a first-class decision label since passive keep-everything is the failure mode being prevented). Added test_curator_review_prompt_is_umbrella_first asserting the umbrella framing, class-level thinking, references/ + templates/ + scripts/ support-file mentions, and the 'use_count is not evidence of value' pre-emption. Added test_curator_review_prompt_offers_support_file_actions asserting skill_manage action=create and action=write_file are both named. **Live validation on author's setup:** - Run 1 (old prompt): 3 archives, stopped after surveying — typical passive outcome - Run 2 (consolidation prompt): 44 archives, 3 patches, surfaced the 50-skill mlops reorg duplicate bug but didn't umbrella - Run 3 (this prompt): 249 archives + 18 new class-level umbrellas created, reducing agent-created skills from 346 → 118 with every archived skill's content preserved as references/ under its umbrella. Pinned skill untouched. Full report in PR description.
This commit is contained in:
@@ -359,13 +359,19 @@ def test_state_atomic_write_no_tmp_leftovers(curator_env):
|
||||
def test_curator_review_prompt_has_invariants():
|
||||
"""Core invariants must be in the review prompt text."""
|
||||
from agent.curator import CURATOR_REVIEW_PROMPT
|
||||
assert "MUST NOT" in CURATOR_REVIEW_PROMPT
|
||||
assert "MUST NOT" in CURATOR_REVIEW_PROMPT or "DO NOT" in CURATOR_REVIEW_PROMPT
|
||||
assert "bundled" in CURATOR_REVIEW_PROMPT.lower()
|
||||
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", "consolidate"):
|
||||
# Must describe the actions the reviewer can take. The exact vocabulary
|
||||
# has tightened over time (the umbrella-first prompt drops 'keep' as a
|
||||
# first-class decision verb, since passive keep-everything is the
|
||||
# failure mode the prompt is trying to avoid), but the core merge /
|
||||
# archive / patch trio must remain callable.
|
||||
for verb in ("patch", "archive"):
|
||||
assert verb in CURATOR_REVIEW_PROMPT.lower()
|
||||
# Must mention consolidation (possibly via "merge" or "consolidat")
|
||||
assert "consolidat" in CURATOR_REVIEW_PROMPT.lower() or "merge" in CURATOR_REVIEW_PROMPT.lower()
|
||||
|
||||
|
||||
def test_curator_review_prompt_points_at_existing_tools_only():
|
||||
@@ -400,6 +406,44 @@ def test_curator_does_not_instruct_model_to_pin():
|
||||
)
|
||||
|
||||
|
||||
def test_curator_review_prompt_is_umbrella_first():
|
||||
"""The curator prompt must push umbrella-building / class-level thinking,
|
||||
not pair-level 'are these two the same?' analysis."""
|
||||
from agent.curator import CURATOR_REVIEW_PROMPT
|
||||
lower = CURATOR_REVIEW_PROMPT.lower()
|
||||
# Must frame the task as active umbrella-building, not a passive audit.
|
||||
assert "umbrella" in lower, (
|
||||
"must use UMBRELLA framing — the class-first abstraction the curator "
|
||||
"is designed to produce"
|
||||
)
|
||||
# Must tell the reviewer not to stop at pair-level distinctness.
|
||||
assert "class" in lower, "must reference class-level thinking"
|
||||
# Must cover the three consolidation methods explicitly
|
||||
assert "references/" in CURATOR_REVIEW_PROMPT, (
|
||||
"must name references/ as a demotion target for session-specific content"
|
||||
)
|
||||
# templates/ and scripts/ make the umbrella a real class-level skill
|
||||
assert "templates/" in CURATOR_REVIEW_PROMPT
|
||||
assert "scripts/" in CURATOR_REVIEW_PROMPT
|
||||
# Must say the counter argument: usage=0 is not a reason to skip
|
||||
assert "use_count" in CURATOR_REVIEW_PROMPT or "counter" in lower, (
|
||||
"must pre-empt the 'usage counters are zero, I can't judge' bailout"
|
||||
)
|
||||
|
||||
|
||||
def test_curator_review_prompt_offers_support_file_actions():
|
||||
"""Support-file demotion (references/templates/scripts) must be one of
|
||||
the three consolidation methods, alongside merge-into-existing and
|
||||
create-new-umbrella."""
|
||||
from agent.curator import CURATOR_REVIEW_PROMPT
|
||||
# skill_manage action=write_file is how references/ are added to an
|
||||
# existing skill — this is the create-adjacent action the curator needs
|
||||
# to demote narrow siblings without touching their SKILL.md.
|
||||
assert "write_file" in CURATOR_REVIEW_PROMPT
|
||||
# Must offer creating a brand-new umbrella when no existing one fits
|
||||
assert "action=create" in CURATOR_REVIEW_PROMPT or "create a new umbrella" in CURATOR_REVIEW_PROMPT.lower()
|
||||
|
||||
|
||||
|
||||
def test_cli_unpin_refuses_bundled_skill(curator_env, capsys):
|
||||
"""hermes curator unpin must refuse bundled/hub skills too (matches pin)."""
|
||||
|
||||
Reference in New Issue
Block a user