mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Salvaged from #72422 by @virtuadex (conflicts resolved against current main; superseded slash-command hunks dropped): - SECURITY.md + SECURITY.es.md: gateway adapters live under plugins/platforms/<name>/, registry in gateway/platform_registry.py - gateway-internals.md (EN + zh-Hans): key-files table rows for platform_registry.py and plugins/platforms/, deferred-loading section - slash-commands.md: /reasoning full level list (max/ultra) + --global; CLI-only notes list gains /prompt, /pet, /hatch, /timestamps - cron.md + cron-script-only.md: script runner accuracy — bash resolved from PATH with /bin/bash fallback, script paths confined to ~/.hermes/scripts/, provider credentials stripped via _sanitize_subprocess_env - curator.md: cron-referenced skills protected from auto-archive, never-used grace floor
358 lines
21 KiB
Markdown
358 lines
21 KiB
Markdown
---
|
||
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](/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.
|
||
|
||
By default (`prune_builtins: true`) the curator can archive **unused bundled built-in skills** (shipped with the repo) after `archive_after_days` of non-use, alongside the agent-created skills it primarily manages. Hub-installed skills (from [agentskills.io](https://agentskills.io)) are always off-limits. Set `curator.prune_builtins: false` to restore the old agent-created-only behavior, where bundled skills are never touched. The curator 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.
|
||
|
||
:::info First-run behavior
|
||
On a brand-new install (or the first time a pre-curator install ticks after `hermes update`), the curator **does not run immediately**. The first observation seeds `last_run_at` to "now" and defers the first real pass by one full `interval_hours`. This gives you a full interval to review your skill library, pin anything important, or opt out entirely before the curator ever touches it.
|
||
|
||
If you want to see what the curator *would* do before it runs for real, run `hermes curator run --dry-run` — it produces the same review report without mutating the library.
|
||
:::
|
||
|
||
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.
|
||
- **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.
|
||
|
||
:::info Consolidation is opt-in
|
||
By default the curator only **prunes** — the deterministic inactivity pass marks skills stale and archives long-unused ones. The opinionated LLM **consolidation** pass (umbrella-building, merging overlapping skills) is off by default because it costs aux-model tokens on every run and makes broad structural changes to your library. Turn it on with `curator.consolidate: true`, or run it once on demand with `hermes curator run --consolidate`.
|
||
:::
|
||
|
||
Pinned skills are off-limits to both the curator's auto-transitions and the agent's own `skill_manage` tool. See [Pinning a skill](#pinning-a-skill) below.
|
||
|
||
## 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
|
||
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)
|
||
```
|
||
|
||
To disable entirely, set `curator.enabled: false`. To keep the always-on pruning but opt into LLM consolidation, set `curator.consolidate: true`.
|
||
|
||
### Running the review on a cheaper aux model
|
||
|
||
The curator's LLM review pass is a regular auxiliary task slot — `auxiliary.curator` — alongside Vision, Compression, Session Search, etc. "Auto" means "use my main chat model"; override the slot to pin a specific provider + model for the review pass instead.
|
||
|
||
**Easiest — `hermes model`:**
|
||
|
||
```bash
|
||
hermes model # → "Auxiliary models — side-task routing"
|
||
# → pick "Curator" → pick provider → pick model
|
||
```
|
||
|
||
The same picker is available in the web dashboard under the **Models** tab.
|
||
|
||
**Direct config.yaml (equivalent):**
|
||
|
||
```yaml
|
||
auxiliary:
|
||
curator:
|
||
provider: openrouter
|
||
model: google/gemini-3-flash-preview
|
||
timeout: 600 # generous — reviews can take several minutes
|
||
```
|
||
|
||
Leaving `provider: auto` (the default) routes the review pass through whatever your main chat model is, matching the behavior of every other auxiliary task.
|
||
|
||
:::note Legacy config
|
||
Earlier releases used a one-off `curator.auxiliary.{provider,model}` block. That path still works but emits a deprecation log line — please migrate to `auxiliary.curator` above so the curator shares the same plumbing (`hermes model`, dashboard Models tab, `base_url`, `api_key`, `timeout`, `extra_body`) as every other aux task.
|
||
:::
|
||
|
||
## CLI
|
||
|
||
```bash
|
||
hermes curator status # last run, counts, pinned list, LRU top 5
|
||
hermes curator run # trigger a run now (blocks until done). Prune-only unless curator.consolidate: true
|
||
hermes curator run --consolidate # force the LLM consolidation pass on for this run, overriding the config default
|
||
hermes curator run --background # fire-and-forget: start the run in a background thread
|
||
hermes curator run --dry-run # preview only — report without any mutations
|
||
hermes curator backup # take a manual snapshot of ~/.hermes/skills/
|
||
hermes curator rollback # restore from the newest snapshot
|
||
hermes curator rollback --list # list available snapshots
|
||
hermes curator rollback --id <ts> # restore a specific snapshot
|
||
hermes curator rollback -y # skip the confirmation prompt
|
||
hermes curator pause # stop runs until resumed
|
||
hermes curator resume
|
||
hermes curator pin <skill> # never auto-transition this skill
|
||
hermes curator unpin <skill>
|
||
hermes curator adopt <skill> # hand an unmanaged skill to the curator
|
||
hermes curator adopt --all-unmanaged # hand over every unmanaged skill
|
||
hermes curator list-unmanaged # itemize skills with no provenance marker
|
||
hermes curator restore <skill> # move an archived skill back to active
|
||
hermes curator list-archived # list skills currently in ~/.hermes/skills/.archive/
|
||
hermes curator archive <skill> # manually archive a single skill now
|
||
hermes curator prune [--days N] # bulk-archive agent-created skills idle >= N days (default 90)
|
||
```
|
||
|
||
## Backups and rollback
|
||
|
||
Before every real curator pass, Hermes takes a tar.gz snapshot of `~/.hermes/skills/` at `~/.hermes/skills/.curator_backups/<utc-iso>/skills.tar.gz`. If a pass archives or consolidates something you didn't want touched, you can undo the whole run with one command:
|
||
|
||
```bash
|
||
hermes curator rollback # restore newest snapshot (with confirmation)
|
||
hermes curator rollback -y # skip the prompt
|
||
hermes curator rollback --list # see all snapshots with reason + size
|
||
```
|
||
|
||
The rollback itself is reversible: before replacing the skills tree, Hermes takes another snapshot tagged `pre-rollback to <target-id>`, so a mistaken rollback can be undone by rolling forward to that one with `--id`.
|
||
|
||
You can also take manual snapshots at any time with `hermes curator backup --reason "before-refactor"`. The `--reason` string lands in the snapshot's `manifest.json` and is shown in `--list`.
|
||
|
||
Snapshots are pruned to `curator.backup.keep` (default 5) to keep disk usage bounded:
|
||
|
||
```yaml
|
||
curator:
|
||
backup:
|
||
enabled: true
|
||
keep: 5
|
||
```
|
||
|
||
Set `curator.backup.enabled: false` to disable automatic snapshotting. The manual `hermes curator backup` command still works when backups are disabled only if you set `enabled: true` first — the flag gates both paths symmetrically so there's no way to accidentally skip the pre-run snapshot on mutating runs.
|
||
|
||
`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
|
||
|
||
The curator only manages skills explicitly marked as **agent-created** in
|
||
`~/.hermes/skills/.usage.json`. A skill qualifies when ALL of the following
|
||
are true:
|
||
|
||
1. Its name is **not** in `~/.hermes/skills/.bundled_manifest` (bundled skills shipped with the repo).
|
||
2. Its name is **not** in `~/.hermes/skills/.hub/lock.json` (hub-installed skills).
|
||
3. Its `.usage.json` entry has `"created_by": "agent"` or `"agent_created": true`.
|
||
|
||
Currently, only the **background self-improvement review fork** sets this marker
|
||
— when it creates a new umbrella skill during its periodic review pass (~every 10
|
||
agent turns). The background fork runs with a write origin of `"background_review"`
|
||
(via `tools/skill_provenance.py`), which is the only path that triggers the
|
||
`mark_agent_created()` call in `skill_manage`.
|
||
|
||
Skills the foreground agent creates via `skill_manage(action="create")` during a
|
||
conversation are **not** marked as agent-created — they are considered
|
||
user-directed and the curator intentionally leaves them alone.
|
||
|
||
:::warning Your hand-written skills are NOT curated
|
||
If you manually created a `SKILL.md` or pointed Hermes at an external skill
|
||
directory, that skill will have a `.usage.json` entry with `created_by: null`
|
||
(or the field absent). The curator will not touch it. The same applies to
|
||
skills the foreground agent created at your request.
|
||
|
||
**To see which skills the curator actually manages**, run `hermes curator status`.
|
||
If the agent-created count is 0, no skills are currently in the curator's
|
||
jurisdiction — the LLM review pass is skipped and the report will show
|
||
`Model: (not resolved) via (not resolved)` with `Duration: 0s`.
|
||
:::
|
||
|
||
### Adopting unmanaged skills
|
||
|
||
`hermes curator status` reports an **unmanaged** count alongside the managed
|
||
one:
|
||
|
||
```
|
||
curator-managed skills: 43 total (agent-created=43 bundled=0)
|
||
active 41
|
||
stale 2
|
||
archived 0
|
||
|
||
unmanaged (no provenance marker): 112 total
|
||
pre-dates marker 34
|
||
foreground-created 78
|
||
never auto-staled or archived — `hermes curator adopt <name>` hands one over
|
||
```
|
||
|
||
Those 112 are curation-*eligible* but permanently invisible to the lifecycle,
|
||
for one of two reasons:
|
||
|
||
- **pre-dates marker** — the record was written before `created_by` existed, so
|
||
it carries no provenance signal at all. Authorship is genuinely unknowable
|
||
from the record.
|
||
- **foreground-created** — a foreground `skill_manage(create)` left the marker
|
||
unset by design, since skills you ask for belong to you.
|
||
|
||
A large library can therefore look fully curated while most of it is
|
||
untouchable. `adopt` closes that gap by **declaration**:
|
||
|
||
```bash
|
||
hermes curator list-unmanaged # itemize them, with reasons
|
||
hermes curator adopt <name> [<name> ...] # hand specific skills over
|
||
hermes curator adopt --all-unmanaged --dry-run # preview the full list
|
||
hermes curator adopt --all-unmanaged # hand over everything (prompts)
|
||
hermes curator adopt --all-unmanaged --yes # skip the prompt
|
||
```
|
||
|
||
Adoption writes the same `created_by: agent` marker the background review fork
|
||
writes. It does **not** reset the inactivity clock — an adopted skill keeps its
|
||
existing `last_activity_at`, so handing over a library you already stopped
|
||
using does not buy it a fresh 90-day window. Expect adopted long-idle skills to
|
||
go `stale` (or `archived`) on the next pass; that is the point.
|
||
|
||
Adoption is also what unblocks autonomous *improvement*. The background review
|
||
fork refuses to patch a skill that isn't curator-managed, so if it notices one
|
||
of your skills is outdated it will say so and recommend adoption rather than
|
||
edit it. Foreground (user-directed) edits are never affected — you and the
|
||
agent can always edit your own skills on request.
|
||
|
||
:::note `created_by` is a policy flag, not a provenance claim
|
||
The stored field is named `created_by`, but it is consumed as "may autonomous
|
||
curation touch this?" — not "who wrote this file". Those are different
|
||
questions, and for records predating the marker the authorship answer is simply
|
||
unrecoverable. The name is kept because it is already on disk in every
|
||
`.usage.json`; read it as policy. `hermes curator adopt` changes the policy, and
|
||
says nothing about who authored the file.
|
||
:::
|
||
|
||
:::note Provenance is declared, never inferred
|
||
Adoption is deliberately manual. Telemetry cannot establish authorship: a skill
|
||
with thousands of patches proves the agent **maintains** it, not that the agent
|
||
**wrote** it — Hermes edits user-authored skills on your behalf constantly. An
|
||
automatic "looks agent-made, adopt it" heuristic would eventually archive
|
||
something you hand-wrote. `adopt` refuses bundled, hub-installed, external, and
|
||
protected built-in skills, which have an owner other than you.
|
||
:::
|
||
|
||
Skills that ARE agent-created follow the full lifecycle:
|
||
|
||
- `active` → (30d unused) `stale` → (90d unused) `archived`
|
||
- Pinned skills bypass all auto-transitions
|
||
- Archives are recoverable via `hermes curator restore <name>`
|
||
|
||
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 <name>`. See the next
|
||
section.
|
||
|
||
## Pinning a skill
|
||
|
||
Pinning protects a skill from deletion — both the curator's automated archive passes and the agent's `skill_manage(action="delete")` tool call. Once a skill is pinned:
|
||
|
||
- The **curator** skips it during auto-transitions (`active → stale → archived`), and its LLM review pass is instructed to leave it alone.
|
||
- The **agent's `skill_manage` tool** refuses `delete` on it, pointing the user at `hermes curator unpin <name>`. Patches and edits still go through, so the agent can improve a pinned skill's content as pitfalls come up without a pin/unpin/re-pin dance.
|
||
|
||
Pin and unpin with:
|
||
|
||
```bash
|
||
hermes curator pin <skill>
|
||
hermes curator unpin <skill>
|
||
```
|
||
|
||
The flag is stored as `"pinned": true` on the skill's entry in `~/.hermes/skills/.usage.json`, so it survives across sessions.
|
||
|
||
Skills named in any cron job's `skills:` list are protected the same way for **auto-transitions** (the curator never stales/archives them while the reference remains), even when the job is paused or disabled. Prefer an explicit pin when you also want `skill_manage delete` blocked.
|
||
|
||
Only **agent-created** skills can be pinned — `hermes curator pin` refuses on bundled and hub-installed skills with an explanatory message if you try. Hub-installed skills are never subject to curator mutation. Bundled built-in skills are only touched when `curator.prune_builtins: true` (the default), and even then only archived after `archive_after_days` of non-use — never patched, consolidated, or deleted. Set `curator.prune_builtins: false` to exempt bundled skills entirely.
|
||
|
||
A small set of **protected built-ins** is hardcoded as never-archivable and never-consolidatable, regardless of `curator.prune_builtins`, pin state, or LLM judgment. These back load-bearing UX — for example, `plan` powers the `/plan` slash-command flow — so silently archiving one would turn its slash command into an "Unknown command" error with no signal to you. Protected built-ins are filtered out of the curator's candidate list entirely, so the consolidation pass never sees them.
|
||
|
||
If you want a stronger guarantee than "no deletion" — for instance, freezing a skill's content entirely while the agent still reads it — edit `~/.hermes/skills/<name>/SKILL.md` directly with your editor. The pin guards tool-driven deletion, not your own filesystem access.
|
||
|
||
## 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`.
|
||
|
||
:::note No candidates? Report shows `(not resolved)`
|
||
When the curator has **no agent-created skills** to review, the LLM review pass
|
||
is skipped entirely. The report header will show
|
||
`Model: (not resolved) via (not resolved)` with `Duration: 0s` — this does **not**
|
||
indicate a configuration error or model resolution failure. It simply means there
|
||
were no candidates, so no model was ever invoked. The auto-transition phase still
|
||
runs and reports its counts normally.
|
||
:::
|
||
|
||
### Rename map in the summary
|
||
|
||
If a run consolidated multiple skills under an umbrella (or merged near-duplicates), the user-visible summary printed at the end of the run includes an explicit rename map showing every `old-name → new-name` pair the curator applied. This is in addition to per-skill transition lines, so when a wave of renames lands you can spot them at a glance without diffing the JSON report. The hint also surfaces under `hermes curator pin` so you can pin the umbrella name immediately if you want to lock the new label in.
|
||
|
||
## Restoring an archived skill
|
||
|
||
If the curator archived something you still want:
|
||
|
||
```bash
|
||
hermes curator restore <skill-name>
|
||
```
|
||
|
||
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](/user-guide/features/skills) — how skills work in general and the self-improvement loop that creates them
|
||
- [Memory](/user-guide/features/memory) — a parallel background review that maintains long-term memory
|
||
- [Bundled Skills Catalog](/reference/skills-catalog)
|
||
- [Issue #7816](https://github.com/NousResearch/hermes-agent/issues/7816) — original proposal and design discussion
|