diff --git a/agent/curator.py b/agent/curator.py index f6008da773..d0854145a5 100644 --- a/agent/curator.py +++ b/agent/curator.py @@ -26,7 +26,7 @@ from utils import atomic_json_write logger = logging.getLogger(__name__) DEFAULT_INTERVAL_HOURS, DEFAULT_MIN_IDLE_HOURS = 24 * 7, 2 # 7 days -DEFAULT_STALE_AFTER_DAYS, DEFAULT_ARCHIVE_AFTER_DAYS = 30, 90 +DEFAULT_STALE_AFTER_DAYS, DEFAULT_ARCHIVE_AFTER_DAYS = 14, 30 # The LLM consolidation fork is opt-in; the deterministic inactivity prune # (apply_automatic_transitions) always runs when the curator is enabled. DEFAULT_CONSOLIDATE = False diff --git a/hermes_cli/config_defaults.py b/hermes_cli/config_defaults.py index 41b846a56f..ea5bdac14f 100644 --- a/hermes_cli/config_defaults.py +++ b/hermes_cli/config_defaults.py @@ -1381,8 +1381,8 @@ DEFAULT_CONFIG = { "enabled": True, "interval_hours": 24 * 7, # hours between runs "min_idle_hours": 2, # only run after the agent has been idle this long - "stale_after_days": 30, # mark "stale" after this many unused days - "archive_after_days": 90, # move to skills/.archive/ (recoverable) after this many + "stale_after_days": 14, # mark "stale" after this many unused days + "archive_after_days": 30, # move to skills/.archive/ (recoverable) after this many # LLM consolidation (umbrella-building) pass. OFF = deterministic inactivity prune only, no # aux-model cost. `hermes curator run --consolidate` overrides once. "consolidate": False, @@ -2380,7 +2380,7 @@ DEFAULT_CONFIG = { # Extra ports detection probes for an external llama-server (besides 8080). "detect_ports": [], }, - "_config_version": 43, # Config schema version - bump this when adding new required fields + "_config_version": 44, # Config schema version - bump this when adding new required fields } diff --git a/hermes_cli/config_migrations.py b/hermes_cli/config_migrations.py index f4e86788c5..9b102c758b 100644 --- a/hermes_cli/config_migrations.py +++ b/hermes_cli/config_migrations.py @@ -647,6 +647,19 @@ MIGRATIONS: Tuple[Tuple[int, Callable[[Dict[str, Any], bool], None]], ...] = ( " ✓ Removed gateway.multiplex_profile_allowlist — the multiplexing gateway now serves " "every profile under profiles/. Delete or archive a profile you do not want served."), extra_guard=lambda raw: "multiplex_profile_allowlist" in raw)), + # 43 → 44: curator prunes faster — stale 30→14 days, archive 90→30 days. A skill nobody has + # touched in a month is prompt weight, not knowledge; archival is recoverable. Only the OLD + # defaults are rewritten; an explicit user value is preserved. + (44, _rewrite_stale_default( + section="curator", key="stale_after_days", old=30, new=14, + added="curator.stale_after_days=14 (was: 30)", + message=" ✓ curator.stale_after_days 30→14 — unused skills are flagged stale after two weeks.")), + (44, _rewrite_stale_default( + section="curator", key="archive_after_days", old=90, new=30, + added="curator.archive_after_days=30 (was: 90)", + message=( + " ✓ curator.archive_after_days 90→30 — skills unused for a month are archived to " + "skills/.archive/ (recoverable with `hermes curator restore`). Set it back to 90 to keep the old window."))), ) diff --git a/hermes_cli/curator.py b/hermes_cli/curator.py index 9111e37970..15b8048e4b 100644 --- a/hermes_cli/curator.py +++ b/hermes_cli/curator.py @@ -332,8 +332,11 @@ def _idle_days(record: dict) -> Optional[int]: def _cmd_prune(args) -> int: """Bulk-archive curator-managed skills idle for >= N days (pinned exempt, archived skipped).""" + from agent import curator from tools import skill_usage - days = getattr(args, "days", 90) + days = getattr(args, "days", None) + if days is None: + days = curator.get_archive_after_days() if days < 1: print(f"curator: --days must be >= 1 (got {days})", file=sys.stderr) return 2 @@ -641,9 +644,10 @@ _SUBCOMMANDS = ( ("archive", "Manually archive a skill (move to .archive/, excluded from prompt)", _cmd_archive, _SKILL), ( - "prune", "Bulk-archive curator-managed skills idle for >= N days (default 90)", _cmd_prune, - _arg("--days", type=int, default=90, - help="Archive skills idle for at least N days (default: 90)"), + "prune", "Bulk-archive curator-managed skills idle for >= N days (default: curator.archive_after_days)", + _cmd_prune, + _arg("--days", type=int, default=None, + help="Archive skills idle for at least N days (default: curator.archive_after_days, 30)"), _YES, _arg("--dry-run", dest="dry_run", **_STORE_TRUE, help="Show what would be archived without doing it")), diff --git a/tests/agent/test_curator.py b/tests/agent/test_curator.py index b898c14f9d..ae8bff7307 100644 --- a/tests/agent/test_curator.py +++ b/tests/agent/test_curator.py @@ -76,8 +76,8 @@ def test_curator_defaults(curator_env): c = curator_env["curator"] assert c.get_interval_hours() == 24 * 7 # 7 days assert c.get_min_idle_hours() == 2 - assert c.get_stale_after_days() == 30 - assert c.get_archive_after_days() == 90 + assert c.get_stale_after_days() == 14 + assert c.get_archive_after_days() == 30 diff --git a/tests/hermes_cli/test_config.py b/tests/hermes_cli/test_config.py index 1dce235334..922a82d46b 100644 --- a/tests/hermes_cli/test_config.py +++ b/tests/hermes_cli/test_config.py @@ -1025,6 +1025,24 @@ class TestRetiredMultiplexAllowlist: assert "multiplex_profile_allowlist" not in DEFAULT_CONFIG["gateway"] +class TestCuratorFasterPrune: + def test_v44_rewrites_old_curator_defaults_but_keeps_user_values(self, tmp_path, monkeypatch): + """Old 30/90 defaults move to 14/30; an explicitly customized window is untouched.""" + from hermes_cli.config import DEFAULT_CONFIG + from hermes_cli.config_migrations import run_migrations + + config_path = tmp_path / "config.yaml" + config_path.write_text(yaml.safe_dump({ + "_config_version": 43, + "curator": {"stale_after_days": 30, "archive_after_days": 180}, + }), encoding="utf-8") + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + run_migrations(43, {"env_added": [], "config_added": [], "warnings": []}, quiet=True) + raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) + assert raw["curator"]["stale_after_days"] == DEFAULT_CONFIG["curator"]["stale_after_days"] + assert raw["curator"]["archive_after_days"] == 180 + + class TestCustomProviderCompatibility: """Custom provider compatibility across legacy and v12+ config schemas. diff --git a/website/docs/user-guide/features/curator.md b/website/docs/user-guide/features/curator.md index 94b610d6e8..1827bc7ad6 100644 --- a/website/docs/user-guide/features/curator.md +++ b/website/docs/user-guide/features/curator.md @@ -35,7 +35,7 @@ If you want to see what the curator *would* do before it runs for real, run `her A run has two phases: -1. **Automatic transitions** (deterministic, no LLM). Skills unused for `stale_after_days` (30) become `stale`; skills unused for `archive_after_days` (90) are moved to `~/.hermes/skills/.archive/`. This is the always-on pruning behavior — it runs whenever the curator is enabled, with no aux-model cost. +1. **Automatic transitions** (deterministic, no LLM). Skills unused for `stale_after_days` (14) become `stale`; skills unused for `archive_after_days` (30) are moved to `~/.hermes/skills/.archive/`. This is the always-on pruning behavior — it runs whenever the curator is enabled, with no aux-model cost. - **Pinned skills** and **skills referenced by any cron job** (including paused/disabled jobs) are skipped entirely — treated like pin for auto-transitions so a slow or paused schedule cannot archive a skill out from under a job. Consolidation also rewrites cron skill references when it merges umbrellas. - **Never-used skills** (`use_count == 0`) get a grace floor: they are not archived until they are at least `stale_after_days` old. Zero uses is absence of evidence, not proof the skill is disposable. 2. **LLM consolidation** (single aux-model pass with a high iteration ceiling — a full curation sweep typically takes 50–100 API calls) — **OFF by default**. When `curator.consolidate: true`, the forked agent surveys the agent-created skills, can read any of them with `skill_view`, and decides per-skill whether to keep, patch (via `skill_manage`), consolidate overlapping ones into class-level umbrellas, or archive via the terminal tool. Consolidation treats a skill as a full package: if a skill has `references/`, `templates/`, `scripts/`, `assets/`, or relative links to those paths, the curator must either keep it standalone, re-home the needed support files and rewrite paths, or archive the entire package unchanged — not flatten only `SKILL.md` into another skill's `references/` file. @@ -55,8 +55,8 @@ curator: enabled: true interval_hours: 168 # 7 days min_idle_hours: 2 - stale_after_days: 30 - archive_after_days: 90 + stale_after_days: 14 + archive_after_days: 30 consolidate: false # LLM umbrella-building pass — opt-in (prune-only by default) prune_builtins: true # archive unused bundled built-in skills too (hub skills always exempt) ``` @@ -115,7 +115,7 @@ hermes curator list-unmanaged # itemize skills with no provenance marker hermes curator restore # move an archived skill back to active hermes curator list-archived # list skills currently in ~/.hermes/skills/.archive/ hermes curator archive # manually archive a single skill now -hermes curator prune [--days N] # bulk-archive agent-created skills idle >= N days (default 90) +hermes curator prune [--days N] # bulk-archive agent-created skills idle >= N days (default: `archive_after_days`, 30) hermes curator ledger # list the per-mutation audit ledger (all actors) hermes curator ledger --skill --limit 50 # filter/paginate ledger entries hermes curator rollback # undo a single mutation from the ledger diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/curator.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/curator.md index e0996056aa..2a5d51008e 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/curator.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/curator.md @@ -31,7 +31,7 @@ Curator 由空闲检查触发,而非 cron 守护进程。在 CLI 会话启动 一次运行分为两个阶段: -1. **自动状态转换**(确定性,无 LLM)。未使用时间超过 `stale_after_days`(30 天)的技能变为 `stale`;未使用时间超过 `archive_after_days`(90 天)的技能被移至 `~/.hermes/skills/.archive/`。 +1. **自动状态转换**(确定性,无 LLM)。未使用时间超过 `stale_after_days`(14 天)的技能变为 `stale`;未使用时间超过 `archive_after_days`(30 天)的技能被移至 `~/.hermes/skills/.archive/`。 2. **LLM 审查**(单次辅助模型 pass,`max_iterations=8`)。派生的 agent 审查 agent 创建的技能,可通过 `skill_view` 读取任意技能,并逐技能决定是保留、修补(通过 `skill_manage`)、合并重叠项,还是通过终端工具归档。 已固定(pinned)的技能对 curator 的自动状态转换和 agent 自身的 `skill_manage` 工具均不可操作。详见下方[固定技能](#pinning-a-skill)。 @@ -45,8 +45,8 @@ curator: enabled: true interval_hours: 168 # 7 days min_idle_hours: 2 - stale_after_days: 30 - archive_after_days: 90 + stale_after_days: 14 + archive_after_days: 30 ``` 若要完全禁用,设置 `curator.enabled: false`。