Skip to content

D141 — Hermes is a supported provider that receives deliveries, not installations

hermes is a provider in the registry (D137) with exactly one supported kind, skill, and four explicitly unsupported cells. Its skills are delivered to $HERMES_HOME/skill-inbox/<name>/cartographer/ — the skill's files, provenance-stamped as everywhere else (D138), plus a generated SOURCE.md — and the agent adopts what it chooses through its own skill_manage tool. Nothing is ever written under $HERMES_HOME/skills/.

Why delivery and not installation. That directory belongs to Hermes' own curator, which archives unused skills, keeps telemetry and honours pins by rewriting what it owns from its learning loop. Materializing there would put Cartographer and the curator in a permanent fight — one overwriting from the server, the other rewriting from its own experience — and the loser is the agent's accumulated learning, which no sync can restore. The convention for proposing a skill from outside already existed (skill-inbox/), and it is a curatorial handoff, not an install.

The four unsupported cells, each for a stated reason rather than by omission: agent — no native subagent directory; hook — no hook mechanism at all, nothing fires at conversation start; mcpconfig.yaml is rendered by an Ansible role and recreated on the next playbook run; instructionsSOUL.md, its always-on slot, is operator-owned and rendered from a template. Anything Cartographer wrote into the last two would be silently destroyed by the next deploy. Unsupported already means "no approval unblocks it" everywhere in the client (D50), so nothing else changes. Its trigger is therefore the scheduled timer from D140: with no session hook, the timer is Hermes' Layer 1.

No timestamp in the delivery path, deliberately departing from the skill-inbox/<skill>/<timestamp>/ convention. Provisioning must be idempotent: a timestamped directory per sync would accumulate a fresh copy every timer tick and no prune could tell stale from current. One stable directory per (skill, source) is updated in place — the last path segment names the proposer, so another source never collides — and the history of the proposal lives in the KB's git log, which is where it belongs. SOURCE.md carries the artifact's content hash, so the agent distinguishes an unchanged re-delivery from a new proposal. SOURCE.md is generated, so a KB skill shipping its own is a collision: that one artifact fails with a warning naming it and is not recorded in the lockfile, while the rest of the sync completes.

Per-provider base directories. Every other provider materializes under one shared base dir; Hermes' root is elsewhere on the machine (/opt/data in the reference deployment), so Lock gains an optional base_dir recording the directory its ManagedFile paths are relative to. Empty — every lockfile written before this, and every other provider — means "the lockfile's own directory", so no migration runs and nothing changes meaning. Prune and on-disk verification (D139) read it through LockBaseDir; the lockfile itself stays a single v2 file in the client's target directory with one Lock per provider. $HERMES_HOME unset makes connect hermes fail naming the variable, before anything is written: falling back to the home directory would scatter files where the agent never looks.

Connect says what it does not do. Hermes has no MCP emitter, so connect hermes writes no MCP entry and reports that its endpoint stays the operator's job — silently doing nothing there would read as a bug. For the same reason it is absent from the interactive connect form, which offers the providers whose MCP configuration connect actually writes; Hermes is connected from the CLI, where the missing-$HERMES_HOME failure can be stated plainly.