Hermes Agent Deep Cuts: The Forgetting Loop — Inside the Curator That Stops Skills From Rotting
Part of the Hermes Agent: Deep Cuts series

Hermes Agent Deep Cuts: The Forgetting Loop — Inside the Curator That Stops Skills From Rotting

I am running Hermes Agent v0.20.0 (2026.8.3), and this post is part of the ongoing Deep Cuts series — spotlighting one specific feature that most users walk past.

Today’s feature: the Curator — the background lifecycle pass that keeps agent-created skills from piling up forever. It tracks how often each skill is viewed, used, and patched; moves long-unused skills through active → stale → archived; snapshots your entire skill library before every real run; and can optionally spawn a forked agent to consolidate near-duplicate skills into class-level umbrellas.

The uncomfortable truth it exists for: a learning loop with no forgetting loop is a memory leak. Hermes’ self-improvement loop creates skills from experience — that’s its strongest differentiator. But the original design issue spells out what happens when a system only accumulates: stale environment-specific guidance persists after conditions change (the “learned helplessness” failure mode where a transient failure becomes a permanent skill saying “X never works”), repeated patching drifts, and a long tail of narrow near-duplicates pollutes the catalog and the prompt path. The Curator is the retirement half of the loop: creation on one side, forgetting on the other.

What it actually does

The lifecycle is a small state machine over each skill, keyed off a telemetry sidecar at ~/.hermes/skills/.usage.json — deliberately outside the SKILL.md file, so operational metadata never pollutes user-authored content:

{
  "my-skill": {
    "use_count": 12,
    "view_count": 34,
    "patch_count": 3,
    "last_used_at": "2026-04-24T18:12:03Z",
    "last_viewed_at": "2026-04-23T09:44:17Z",
    "last_patched_at": "2026-04-20T22:01:55Z",
    "created_at": "2026-03-01T14:20:00Z",
    "state": "active",
    "pinned": false,
    "archived_at": null
  }
}

Counters increment on real events: skill_view bumps view_count, loading a skill into a conversation bumps use_count, and skill_manage patch/edit/write_file/remove_file bump patch_count. Bundled and hub-installed skills are excluded from telemetry writes entirely.

A run has two phases, and the split matters:

1. Automatic transitions — deterministic, zero LLM cost, always on. Skills unused for stale_after_days (default 30) become stale; skills unused for archive_after_days (default 90) move to ~/.hermes/skills/.archive/. This is the always-on pruning behavior. Three safety rails on top:

  • Pinned skills and skills referenced by any cron job (including paused/disabled jobs) are skipped entirely — _cron_referenced_skills() in the installed source reads cron.jobs.referenced_skill_names() and is deliberately best-effort (“a cron-module import error or corrupt jobs store must never break the curator”).
  • Never-used skills (use_count == 0) get a grace floor: they are not archived until at least stale_after_days old. Zero uses is absence of evidence, not proof the skill is disposable.
  • Built-ins are seeded, not epoch-dated. When curator.prune_builtins: true (the default), bundled skills are eligible — but the first time the curator sees one it writes a baseline record so the inactivity clock starts now, not at the Unix epoch. A bundled skill is archived only after a fresh 90 days of genuine non-use, never mass-pruned on the first pass.

2. LLM consolidation — off by default. When curator.consolidate: true, the curator forks a background AIAgent (same pattern as the memory/skill self-improvement nudges) that surveys agent-created skills, can read any of them with skill_view, and decides per-skill whether to keep, patch, consolidate overlaps into umbrellas, or archive — a full sweep is “typically 50–100 API calls” per the docs. It treats a skill as a full package: if a skill has references/, templates/, scripts/, or assets/, the curator must keep it standalone, re-home support files and rewrite paths, or archive the entire package — it never flattens only SKILL.md into another skill’s references/ file.

The default posture is deliberately conservative: prune-only, no LLM. The opinionated merge pass is opt-in because it costs auxiliary-model tokens every run and makes broad structural changes.

Why it is obscure

Three reasons. First, it is triggered by inactivity, not by a clock. The docs are explicit: “triggered by an inactivity check, not a cron daemon.” On CLI session start, and on a recurring tick inside the gateway’s housekeeping thread, Hermes checks whether enough time has passed since the last run (interval_hours, default 7 days) — and only then spawns the review fork. I confirmed both call sites in the installed v0.20.0 source:

  • cli.py:15417 — CLI session start kicks maybe_run_curator() in a daemon thread so it never blocks the interactive loop, printing a 💾 summary line.
  • gateway/run.py:27286 — the gateway housekeeping loop polls the curator every CURATOR_EVERY = 60 ticks (hourly at the default 60s interval); the comment is explicit that this is just the poll rate and “the real work only fires once per config interval.”

No cron entry, no systemd timer, no scheduler tab. If you’re looking for a scheduled job, you will never find it — the feature is invisible until you run hermes curator status.

Second, the entire CLI surface is a hidden command family. hermes curator has fifteen subcommands that most users never see: status, usage, run, pause, resume, pin, unpin, list-unmanaged, adopt, restore, list-archived, archive, prune, backup, rollback. The same surface is available in-session as the /curator slash command. There’s no mention of any of this in the skills docs’ happy path — the Curator page is a separate doc buried under Features.

Third, the “smart” part is switched off by default. Every fresh install runs prune-only. The curator.consolidate: false default means the LLM umbrella-building pass — the part that sounds like the headline feature — never fires unless you opt in via curator.consolidate: true or hermes curator run --consolidate.

The provenance gate (the part that surprises people)

Here is the subtle one, and it’s the biggest source of “why isn’t my library being cleaned up?” confusion. The curator only manages skills with an explicit agent-created marker in .usage.json"created_by": "agent". And that marker is written by exactly one path: the background self-improvement review fork, which runs with a write origin of "background_review" (via tools/skill_provenance.py). The docs state it plainly:

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.

So the skills you asked the agent to create — the ones you’d naturally expect a “cleanup” feature to manage — are invisible to it. Hand-written SKILL.md files are invisible too (created_by: null). On a real install the split is stark. Live from this machine’s hermes curator status:

curator-managed skills: 83 total  (agent-created=5  bundled=78)
  active     83
  stale      0
  archived   0

unmanaged (no provenance marker): 4 total
  pre-dates marker    0
  foreground-created  4
  never auto-staled or archived — `hermes curator adopt <name>` hands one over

Five agent-created skills out of 87 tracked. Everything the agent built at my request — the skills this very blog pipeline depends on — sits outside the lifecycle.

The gap is closed by declaration, not inference: hermes curator adopt <skill> writes the same created_by: agent marker the background fork writes. The design deliberately refuses to guess authorship from telemetry — the issue and docs are explicit that “a skill with thousands of patches proves the agent maintains it, not that the agent wrote it.” Adoption also doesn’t reset the inactivity clock, so handing over a library you stopped using means it gets archived on the next pass. That’s the point, the docs note dryly.

The gotchas that break the happy path

Gotcha 1: on a fresh install, it looks dead for a week. The first time the gate runs with no last_run_at on record, it seeds the state to “now” and defers the first real pass by one full interval_hours (7 days). This is deliberate — the docs call it “a full interval to review your skill library, pin anything important, or opt out entirely before the curator ever touches it.” But if you’re testing after hermes update, the feature will appear completely inert for seven days. The escape hatch is explicit invocation: hermes curator run (and --dry-run for a no-mutation preview) bypasses the gate entirely.

Gotcha 2: min_idle_hours is documented but the shipped call sites never enforce it. The docs say “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” — and maybe_run_curator() does implement the check (if idle_for_seconds < min_idle_s: return None). But both shipped call sites in v0.20.0 pass idle_for_seconds=float("inf") — CLI startup comments “CLI startup = fully idle”, and the gateway passes infinity unconditionally. In the shipped integration, the idle gate is effectively a no-op; nothing measures real agent activity before firing. The mechanism exists for future callers, but today the only gates that matter are enabled, paused, and interval_hours.

Gotcha 3: pinning is a deletion lock, not a freeze. hermes curator pin <name> blocks both the curator’s auto-transitions and the agent’s skill_manage(action="delete") — the tool refuses and points you at hermes curator unpin. Patches and edits still go through, which is the right granularity. But there’s a hardcoded escape from the other side: a small set of protected built-ins is never archivable and never consolidatable regardless of prune_builtins, pin state, or LLM judgment — the installed source names plan (it powers the /plan slash-command flow; archiving it would turn the slash command into “Unknown command” with no signal). And hub-installed skills are always exempt — they have an external upstream owner.

Gotcha 4: prune_builtins: true is the default. Bundled skills shipped with the repo can be archived after 90 days of non-use. The docs are careful to note built-ins are normally restored on hermes update, “so pruning them only sticks because a suppression list tells the re-seeder to leave them archived” — but if you assumed “bundled = untouchable,” that’s not the default. curator.prune_builtins: false restores the old agent-created-only behavior.

What I verified live, on this machine

The curator is not hypothetical here — this profile has been running it. The on-disk state at ~/.hermes/skills/.curator_state:

{
  "last_run_at": "2026-08-07T02:16:22.738570+00:00",
  "last_run_duration_seconds": 0.878,
  "last_run_summary": "auto: no changes; llm: skipped (consolidation off)",
  "paused": false,
  "run_count": 1
}

The per-run report at ~/.hermes/logs/curator/20260807-021622/REPORT.md confirms a real pass: 73 agent-created skills checked, zero marked stale, zero archived, Model: (not resolved) via (not resolved) with Duration: 0s — exactly the “no candidates for the LLM pass, so no model was ever invoked” shape the docs describe, because consolidation is off. The pre-run backup exists at .curator_backups/2026-08-07T02-16-22Z/, and the bundled-skill seeding is visible in .usage.json: entries like airtable and apple-notes all carry created_at: 2026-08-07T02:16:23 — one second after the run started, the exact “first time the curator sees them” baseline the source describes.

Then the live CLI, run read-only against the production profile:

hermes curator status
# curator: ENABLED, runs: 1, last run: 4d ago, interval: every 7d
# stale after: 30d unused, archive after: 90d unused, consolidate: off

hermes curator run --dry-run
# curator: running DRY-RUN (report only, no mutations)...
# curator: consolidation is off — running prune-only (deterministic stale/archive)
# auto (preview): 83 candidate skill(s) — no transitions applied in dry-run
# dry-run: no changes applied. Run `hermes curator run` (no flag) to apply.

--dry-run produces the same review report with zero mutations — the safe way to see what the next pass would do. The full command family checks out against the CLI help: status, usage, run, pause, resume, pin, unpin, list-unmanaged, adopt, restore, list-archived, archive, prune, backup, rollback.

When it matters

If you run a Hermes profile long enough to accumulate agent-created skills — which any autonomous profile does, because that’s the whole self-improvement pitch — the Curator is the difference between a catalog that stays navigable and a pile of near-duplicate heuristics that drift and contradict each other. The design choices tell you what the team believes about autonomous systems:

  • Archive, never delete. “The curator also never auto-deletes — the worst outcome is archival into ~/.hermes/skills/.archive/, which is recoverable.” Plus a tar.gz snapshot of the whole skills tree before every real run, with rollback (hermes curator rollback --list, --id <ts>) that is itself reversible via a pre-rollback snapshot. The failure mode they fear is irreversible destruction of learned knowledge, so they bias hard toward recoverability.
  • Deterministic first, LLM second. The always-on pass is pure time math at zero model cost; the opinionated pass is opt-in and explicitly routed through the auxiliary.curator slot (docs: override via hermes model → auxiliary → Curator, or auxiliary.curator.provider/model in config with a generous timeout: 600) so you can pin it to a cheap flash model. A background task that costs 50–100 API calls per week should never silently ride your main chat model.
  • Provenance is declared, never inferred. The adopt flow exists precisely because telemetry can’t establish authorship. The system would rather leave skills unmanaged than guess wrong and archive something you hand-wrote.

The operator consequence: don’t assume the default configuration is curating anything meaningful. On a profile where most skills were created in foreground conversation (as mine are), the curator manages a minority of the library until you adopt them. If you want the forgetting loop to actually cover your library: run hermes curator list-unmanaged, hermes curator adopt --all-unmanaged, consider curator.consolidate: true on a cheap aux slot, and pin anything load-bearing (hermes curator pin <skill>).

Observed: the full curator: config block with defaults in config_defaults.py; both trigger call sites (cli.py session start and gateway/run.py housekeeping, both passing idle_for_seconds=float("inf")); the should_run_now() first-run seeding and interval gate; _cron_referenced_skills() protection incl. paused jobs; the protected-built-ins list naming plan; the auxiliary.curator slot with timeout: 600; .usage.json sidecar structure and bundled-seeding timestamps; live .curator_state, REPORT.md, backup snapshot, hermes curator status and run --dry-run output; the 15-subcommand CLI; docs’ consolidation cost estimate and first-run deferral rationale; issue #7816 scope and the related issues (#6051 learned helplessness, #2045 lazy loading).

Inference: the Curator is the garbage collector for procedural memory — a learning system without a retirement loop accumulates stale heuristics and prompt clutter. The conservative defaults (prune-only, archive-not-delete, declared provenance, pre-run snapshots) read as a deliberate bet that in autonomous systems, the cost of deleting something learnable is higher than the cost of keeping something stale — so they make forgetting reversible and opt-in.

Open questions: the shipped call sites passing inf for the idle measurement means min_idle_hours is dead config today — whether a real activity probe lands is unaddressed in the docs; issue #7816’s remaining items (negative-claim TTL/revalidation to kill the learned-helplessness failure mode, and hide_stale_from_prompt so stale skills are filtered from prompt injection, not just hidden once archived) are still open; and whether adoption semantics get any automation, since the docs are explicit that auto-adopt heuristics are rejected by design.

A self-improving agent that only ever adds is a system that eventually contradicts itself. The Curator is Hermes’ answer to that — and the uncomfortable lesson is that the forgetting half of the loop is opt-in, provenance-gated, and deliberately conservative. If your skills library is growing and nothing is ever archived, the feature isn’t broken. It’s waiting for you to declare what it’s allowed to forget.

Sources

Keep reading