diff --git a/website/docs/user-guide/features/curator.md b/website/docs/user-guide/features/curator.md new file mode 100644 index 0000000000..bf30920513 --- /dev/null +++ b/website/docs/user-guide/features/curator.md @@ -0,0 +1,151 @@ +--- +sidebar_position: 3 +title: "Curator" +description: "Background maintenance for agent-created skills — usage tracking, staleness, archival, and LLM-driven review" +--- + +# Curator + +The curator is a background maintenance pass for **agent-created skills**. It tracks how often each skill is viewed, used, and patched, moves long-unused skills through `active → stale → archived` states, and periodically spawns a short auxiliary-model review that proposes consolidations or patches drift. + +It exists so that skills created via the [self-improvement loop](/docs/user-guide/features/skills#agent-managed-skills-skill_manage-tool) don't pile up forever. Every time the agent solves a novel problem and saves a skill, that skill lands in `~/.hermes/skills/`. Without maintenance, you end up with dozens of narrow near-duplicates that pollute the catalog and waste tokens. + +The curator **never touches** bundled skills (shipped with the repo) or hub-installed skills (from [agentskills.io](https://agentskills.io)). It only reviews skills the agent itself authored. It also **never auto-deletes** — the worst outcome is archival into `~/.hermes/skills/.archive/`, which is recoverable. + +Tracks [issue #7816](https://github.com/NousResearch/hermes-agent/issues/7816). + +## How it runs + +The curator is triggered by an inactivity check, not a cron daemon. On CLI session start, and on a recurring tick inside the gateway's cron-ticker thread, Hermes checks whether: + +1. Enough time has passed since the last curator run (`interval_hours`, default **7 days**), and +2. The agent has been idle long enough (`min_idle_hours`, default **2 hours**). + +If both are true, it spawns a background fork of `AIAgent` — the same pattern used by the memory/skill self-improvement nudges. The fork runs in its own prompt cache and never touches the active conversation. + +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/`. +2. **LLM review** (single aux-model pass, `max_iterations=8`). 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, or archive via the terminal tool. + +Pinned skills bypass all auto-transitions and the LLM is instructed not to touch them. + +## Configuration + +All settings live in `config.yaml` under `curator:` (not `.env` — this isn't a secret). Defaults: + +```yaml +curator: + enabled: true + interval_hours: 168 # 7 days + min_idle_hours: 2 + stale_after_days: 30 + archive_after_days: 90 + auxiliary: + provider: null # null = use main auxiliary client resolution + model: null +``` + +To disable entirely, set `curator.enabled: false`. + +To use a cheaper aux model for the LLM review pass instead of your main model, set `curator.auxiliary.provider` and `curator.auxiliary.model` to something specific (e.g. `openrouter` + `google/gemini-3-flash-preview`). + +## CLI + +```bash +hermes curator status # last run, counts, pinned list, LRU top 5 +hermes curator run # trigger a review now (background by default) +hermes curator run --sync # same, but block until the LLM pass finishes +hermes curator pause # stop runs until resumed +hermes curator resume +hermes curator pin # never auto-transition this skill +hermes curator unpin +hermes curator restore # move an archived skill back to active +``` + +`hermes curator status` also lists the five least-recently-used skills — a quick way to see what's likely to become stale next. + +The same subcommands are available as the `/curator` slash command inside a running session (CLI or gateway platforms). + +## What "agent-created" means + +A skill is considered agent-created if its name is **not** in: + +- `~/.hermes/skills/.bundled_manifest` (skills copied from the repo on install), and +- `~/.hermes/skills/.hub/lock.json` (skills installed via `hermes skills install`). + +Everything else in `~/.hermes/skills/` is fair game for the curator. This includes: + +- Skills the agent saved via `skill_manage(action="create")` during a conversation. +- Skills you created manually with a hand-written `SKILL.md`. +- Skills added via external skill directories you've pointed Hermes at. + +If you want to protect a specific skill from ever being touched — for example a hand-authored skill you rely on — use `hermes curator pin `. The pin is a hard guarantee enforced at the shared mutation helper; no code path that would archive, consolidate, or state-transition a skill can bypass it. + +## Usage telemetry + +The curator maintains a sidecar at `~/.hermes/skills/.usage.json` with one entry per skill: + +```json +{ + "my-skill": { + "use_count": 12, + "view_count": 34, + "last_used_at": "2026-04-24T18:12:03Z", + "last_viewed_at": "2026-04-23T09:44:17Z", + "patch_count": 3, + "last_patched_at": "2026-04-20T22:01:55Z", + "created_at": "2026-03-01T14:20:00Z", + "state": "active", + "pinned": false, + "archived_at": null + } +} +``` + +Counters increment when: + +- `view_count`: the agent calls `skill_view` on the skill. +- `use_count`: the skill is loaded into a conversation's prompt. +- `patch_count`: `skill_manage patch/edit/write_file/remove_file` runs on the skill. + +Bundled and hub-installed skills are explicitly excluded from telemetry writes. + +## Per-run reports + +Every curator run writes a timestamped directory under `~/.hermes/logs/curator/`: + +``` +~/.hermes/logs/curator/ +└── 20260429-111512/ + ├── run.json # machine-readable: full fidelity, stats, LLM output + └── REPORT.md # human-readable summary +``` + +`REPORT.md` is a quick way to see what a given run did — which skills transitioned, what the LLM reviewer said, which skills it patched. Good for auditing without having to grep `agent.log`. + +## Restoring an archived skill + +If the curator archived something you still want: + +```bash +hermes curator restore +``` + +This moves the skill back from `~/.hermes/skills/.archive/` to the active tree and resets its state to `active`. The restore refuses if a bundled or hub-installed skill has since been installed under the same name (would shadow upstream). + +## Disabling per environment + +The curator is on by default. To turn it off: + +- **For one profile only:** edit `~/.hermes/config.yaml` (or the active profile's config) and set `curator.enabled: false`. +- **For just one run:** `hermes curator pause` — the pause persists across sessions; use `resume` to re-enable. + +The curator also refuses to run if `min_idle_hours` hasn't elapsed, so on an active dev machine it naturally only runs during quiet stretches. + +## See also + +- [Skills System](/docs/user-guide/features/skills) — how skills work in general and the self-improvement loop that creates them +- [Memory](/docs/user-guide/features/memory) — a parallel background review that maintains long-term memory +- [Bundled Skills Catalog](/docs/reference/skills-catalog) +- [Issue #7816](https://github.com/NousResearch/hermes-agent/issues/7816) — original proposal and design discussion diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index eff7750ebf..551242b758 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -40,6 +40,19 @@ const config: Config = { // Disabled: appends ?_highlight=... to URLs (before the #anchor), // which makes copy/pasted doc links ugly. Ctrl+F on the page is fine. highlightSearchTermsOnTargetPage: false, + // Exclude the auto-generated per-skill catalog pages from search. + // There are hundreds of them and they dominate results for generic + // terms, drowning out the real user-guide / reference docs. + // The two human-written catalog indexes (reference/skills-catalog, + // reference/optional-skills-catalog) remain indexed. + // + // Note: ignoreFiles matches `route` (baseUrl stripped, no leading + // slash). With baseUrl '/docs/', `/docs/user-guide/skills/bundled/x` + // becomes 'user-guide/skills/bundled/x'. + ignoreFiles: [ + /^user-guide\/skills\/bundled\//, + /^user-guide\/skills\/optional\//, + ], }), ], ], diff --git a/website/sidebars.ts b/website/sidebars.ts index e205bfb249..6f43cdb272 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -44,6 +44,7 @@ const sidebars: SidebarsConfig = { items: [ 'user-guide/features/tools', 'user-guide/features/skills', + 'user-guide/features/curator', 'user-guide/features/memory', 'user-guide/features/memory-providers', 'user-guide/features/context-files',