--- name: hermes-plugin-development description: Build, harden, test, and package standalone native Hermes plugins, including tool registration, profile-scoped configuration and secrets, multiplex isolation, manifests, bundled skills, and real PluginManager compatibility smokes. version: 1.0.0 author: Hermes Agent license: MIT metadata: hermes: tags: [hermes, plugins, multiplex, profiles, security, tdd, testing] related_skills: [hermes-agent, coding-quality-workflow, test-driven-development] --- # Hermes Plugin Development ## When to use Load this skill when creating, modifying, debugging, reviewing, or releasing a standalone/native Hermes plugin. It especially applies when a plugin reads profile config or secrets, registers model tools or bundled skills, supports gateway multiplexing, or must load from `HERMES_HOME/plugins/` rather than from a development checkout. ## Core design rules 1. Keep plugin capability at the edge; do not patch Hermes core for plugin-specific behavior. 2. Handlers return Hermes-compatible JSON strings unless the runtime contract explicitly requires an exception to propagate. 3. Keep model-facing schemas minimal. Tenant/project/profile authority comes from the active runtime profile, never caller-supplied model arguments. 4. Use Hermes-native profile resolution and credential scoping whenever available. Standalone fallbacks are compatibility paths, not alternate authority chains. 5. Fail closed before network or filesystem mutation when required profile config, credentials, or scope is absent. 6. Keep `plugin.yaml` tool/skill declarations exactly synchronized with real registrations. 7. Bundle skills using paths derived from the loaded package/plugin root; do not rely on the checkout working directory. ## Strict-TDD workflow 1. Inspect the plugin manifest, registration entry point, schemas, handlers, config/secret helpers, and existing real-loader test. 2. Write one focused failing behavior test first and run it to record RED. 3. For security boundaries, include hostile ambient state and cross-profile adversarial fixtures rather than only happy-path mocks. 4. Make the smallest production change that turns the focused test GREEN. 5. Repeat for each independent behavior; do not batch unobserved production fixes ahead of tests. 6. Run the focused suite, then the complete plugin suite. 7. Run a real temporary-`HERMES_HOME` `PluginManager.discover_and_load()` smoke without adding the plugin directory to `PYTHONPATH`. 8. Compare exact loader registrations against the manifest, inspect `git diff --check`, stage only plugin-owned files, commit, and rerun post-commit verification. ## Profile and multiplex safety For any profile-aware plugin, treat the process environment as hostile ambient state under multiplexing: - Active config/home must follow Hermes' ContextVar-scoped home resolver. - Active credentials must follow Hermes' ContextVar-scoped secret resolver. - An unscoped credential read in multiplex mode must remain a loud fail-closed error; generic handler wrappers must not swallow it. - Non-secret routing values must come from the active profile's `config.yaml`, not process-global plugin env vars. - Missing Gate/service URLs and tenant/project references must never fall back to localhost or placeholders such as `current`. See `references/multiplex-profile-isolation.md` for the adversarial fixture, exact fallback rules, central tenant/project resolver pattern, and loader/manifest verification recipe. ## Real loader compatibility gate Copy the complete plugin into a temporary `HERMES_HOME/plugins/`, enable it in temporary `config.yaml`, and invoke the installed Hermes repository's real `PluginManager`. Set `PYTHONPATH` to Hermes only. Assert: - plugin enabled with no loader error; - exact expected tool names/count; - exact qualified bundled skill names; - representative handler behavior; and - continued behavior across a second handler call. A test-only `PYTHONPATH=` is invalid evidence because it can hide imports that fail under Hermes' namespaced directory loader. ## Review checklist - [ ] Test was observed failing before each production behavior change. - [ ] No direct process-global profile routing under Hermes. - [ ] No scoped-secret fallback after empty/error/unscoped reads. - [ ] No model-selectable tenant/project authority. - [ ] No localhost or `current` tenant fallback. - [ ] Required config fails before I/O with redacted errors. - [ ] Tool wrappers preserve fail-closed scope exceptions. - [ ] Manifest equals real registrations. - [ ] Focused tests, full plugin suite, and real loader smoke pass. - [ ] Final staged paths are plugin-owned and `git diff --check` passes. ## Common pitfalls - Reading `HERMES_HOME` directly and therefore ignoring the per-turn override. - Reading secret env vars directly and leaking another profile's process-global value. - Catching `UnscopedSecretError` in a broad JSON error wrapper. - Treating a default localhost URL as harmless configuration convenience. - Duplicating project-reference fallback logic across tool modules. - Verifying direct imports but never exercising Hermes' directory-plugin loader. - Checking that one tool loaded while leaving `plugin.yaml` stale for several others.