AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Hermes Plugin Development

skill-atlasomnia-donna-starter-hermes-plugin-development · by AtlasOmnia

hermes-plugin-development — Design, register, and debug Hermes plugins — hooks, YAML wiring, profile detection, token routing patterns.

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add skill-atlasomnia-donna-starter-hermes-plugin-development

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution Used
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-atlasomnia-donna-starter-hermes-plugin-development)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
5d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Hermes Plugin Development? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Hermes Plugin Development

Use when: creating, debugging, or integrating Hermes Agent plugins, including Python backend plugins (tools, hooks, routers, middleware) and JavaScript Hermes Desktop runtime plugins.

Core patterns

  • Choose the extension surface first:
  • Backend plugins live under ~/.hermes/plugins// and add tools, hooks, commands, providers, or middleware.
  • Desktop runtime plugins live under $HERMES_HOME/desktop-plugins//plugin.js and add native UI contributions through @hermes/plugin-sdk. For model-picker specifics, including a Desktop-local /switch command plus backend command-inventory bridge, use references/desktop-model-picker-plugin.md.
  • Resolve the requested interaction surface before building anything. “TUI,” “terminal,” “over SSH,” or “from Windows over SSH” means the prompt_toolkit CLI, not Hermes Desktop. Never substitute a Desktop popover or a backend text-response command.
  • Backend plugin slash handlers return text; they cannot open a prompt_toolkit modal. For a TUI command that opens an existing picker, prefer a central command alias or an update-safe quick_commands alias. If the user requires an explicit Session/Global step, aliasing to bare /model is insufficient because the standard picker follows its persistence default; extend the TUI picker state with a scope stage and verify that scope reaches the final switch call. For live verification, use a fresh CLI under tmux, synchronize on the visible composer, capture each modal stage, then repeat through the user's real SSH path; do not mistake raw PTY repaint fragments for a product failure. See references/tui-model-picker-slash-command.md.
  • Desktop-only slash-like UI commands may use a two-surface integration: composer middleware opens/cancels locally, while an enabled Python backend plugin registers inventory/autocomplete and a non-Desktop fallback. Middleware receives a { text, attachments } draft object—never test it with raw strings. This pattern does not satisfy TUI/SSH requests.
  • Backend plugins use:
  • __init__.py: Python code + hook implementations
  • plugin.yaml: metadata + hook declarations
  • Optional config.yaml: per-profile settings (e.g., routermodel, floortoolsets)
  • A plugin is wired when Hermes discovers it from a supported plugin directory or pip entry point, its manifest is valid, and register(ctx) succeeds.
  • For fail-closed policy plugins deployed across multiple profile homes, use references/fail-closed-policy-plugin-deployment.md: immutable library-to-wrapper staging, canonical-root deduplication, per-profile atomic install/enable, fresh-process and gateway canaries, runtime-cache-aware hash reconciliation, rollback, and stale-session retirement. For malformed controlled-tool schemas, raw-dict result failures, and the source-versus-installed activation boundary, see references/self-gated-policy-plugin-deployment.md.
  • A plugin is not yet proven through an API platform merely because it registers locally. Resolve the active runtime home, confirm the platform-specific toolset allowlist, restart through the approved owner path, and run a live header-to-agent-to-registry-to-handler contract matrix. For trusted X-Hermes-Session-Id propagation, negative/auth, streaming, cancellation, concurrency, restart, lifecycle, and hashed-evidence requirements, use references/api-server-live-plugin-contract.md.
  • Profile-specific activation should be enforced inside plugin config; discovery alone does not mean the plugin should mutate every profile.
  • In a multiplexed gateway, discovery can be process-global while hook execution is request-profile-scoped. If a profile-local plugin is never discovered, install it in the global plugin directory but make every hook fail disabled unless canonical request-scoped get_hermes_home() resolves the intended profiles/ path. For latency-critical read-only lookups, combine that guard with mutation-intent exclusion, deterministic ctx.dispatch_tool() prefetch, bounded context injection, and one-main-call live timing. See references/multiplex-profile-guarded-prefetch.md.
  • Declare only hooks present in the target runtime's live hook registry. For version-dependent hooks, check VALID_HOOKS before registering instead of advertising an unknown hook and producing startup warnings.

Reloading Desktop runtime plugins

Desktop plugins normally hot-reload whenever their plugin.js changes. Use the least disruptive path:

  1. Preferred: save the file and verify the contribution updates.
  2. Manual rescan: in Hermes Desktop, open the command palette (⌘K) and run Reload desktop plugins.
  3. If GUI control is unavailable or denied while the user's reload request still stands, retrigger each existing plugin's file watcher without reloading the renderer:

``bash for f in "${HERMES_HOME:-$HOME/.hermes}"/desktop-plugins/*/plugin.js; do [ -e "$f" ] && touch "$f" done ``

Verify the target files' modification times changed, the Hermes process remains running, and—when UI access is available—the plugin contribution is present with no load-error toast.

Do not substitute Electron's View → Reload unless a full renderer reload is acceptable; it is broader than a desktop-plugin reload and can unnecessarily disturb current UI state. Touching a watched file reloads an already-discovered plugin. A newly added plugin directory may still need the command-palette rescan or the app's periodic directory scan.

YAML wiring (critical)

  • Use provides_hooks: (NOT hooks:).
  • Example:

```yaml name: my-plugin version: 1.0.0 description: "My plugin" kind: standalone requiresenv: [] provideshooks:

  • preagentinit
  • prellmcall
  • posttoolcall

```

  • If hooks are declared but never called, first check provides_hooks vs hooks. This is a common failure mode.

Hook function naming

  • Hook functions must be PUBLIC (no underscore prefix) unless the plugin system explicitly uses them with underscores.
  • Match exactly what you register:

``python def register(ctx): ctx.register_hook("pre_agent_init", pre_agent_init) ctx.register_hook("pre_llm_call", pre_llm_call) ctx.register_hook("post_tool_call", post_tool_call) ``

  • If they are named _pre_llm_call but registered as pre_llm_call, Hermes will never call them.

Profile detection inside plugins

Hermes does NOT reliably set HERMES_PROFILE when using --profile . Don't trust it blindly.

Safe pattern (from the hardened hermes-token-router):

  • Prefer explicit HERMES_PROFILE / HERMES_ACTIVE_PROFILE when present.
  • Otherwise infer only from the canonical HERMES_HOME path (.../profiles/).
  • If identity remains unknown, use disabled/default behavior.
  • Never select the first enabled profile by config insertion order. That can apply another profile's policy to the wrong live agent.

For the user's router experiments, use a dedicated isolated test profile, snapshot the installed plugin/config first, and keep global/default routing disabled.

Token router / tool routing patterns

For plugins that predict or reduce tools:

  • Route once before the first provider request, then keep the surface stable in that live agent process.
  • Prefer an early surface hook when the live hook registry exposes one. Otherwise, current Hermes pre_llm_call can still reduce the actual provider payload, although it runs after the earliest preflight work.
  • Use tool_request middleware to expand a registry-known pruned tool before ordinary validation/dispatch.
  • Keep request_toolset visible as a secondary recovery path. Its schema should accept string toolset names and validate them against the live registry at call time; do not freeze an early-registration enum that may contain only partially registered toolsets.
  • Expand monotonically; never reclassify and shrink the tool surface on every turn.
  • Fail open on ambiguity or errors rather than relying on large permanent floor toolsets.
  • Store state on the agent; key compatibility references by session_id and release them on on_session_end.
  • Use public registry APIs and build recovery choices from the live registry.
  • Keep native desktop intent distinct from web-browser and image-analysis intent: capturing a Safari/Chrome/Finder window requires computer_use, webpage interaction requires browser, and analyzing an existing screenshot requires vision.
  • A plugin tool remains deferrable under progressive tool search even when registered into a core-named toolset such as terminal; non-core tool names are removed before a token router caches the model-facing definitions, so the router cannot restore that tool merely by resolving its toolset. For latency-critical deterministic read-only lookups, either keep the plugin schema visible or perform a real ctx.dispatch_tool() from pre_llm_call, inject the bounded result as turn context, and pair it with a deterministic no-tool route so the turn pays for one main-model call rather than classifier + tool-call + answer rounds. Emit explicit dispatch/completion logs because hook-dispatched tools do not appear as model-authored Tool call: lines, and test that unrelated or mutating intents do not trigger prefetch.
  • Judge routing from live route logs and executed tool names, not final prose alone; a correctly loaded tool can still fail later because the target app/window is unavailable or approval is denied.

See references/tool-router-production-hardening.md for the full implementation, intent-collision pitfalls, and live validation workflow.

Composing with external model-routing gateways

When Hermes is placed behind an OpenAI-compatible model gateway, compose the systems instead of merging repositories: Hermes owns the agent/tool loop, a tool router owns schema reduction, and the gateway owns upstream model choice. Use a separate profile-gated llm_request middleware plugin to inject a stable session-affinity header; do not fold provider routing, credentials, or privacy policy into the tool router.

Keep document/add-in bridge authorization separate from model affinity, use explicit allowlisted model pools, and fail closed for sensitive routes rather than bypassing the gateway through an unrelated cloud fallback. Generic custom providers are the first integration target; specialized Hermes-managed OAuth transports require an independent compatibility design.

Full architecture, privacy rules, and verification gates: references/external-model-gateway-affinity.md.

Runtime hook-composition contract

Never read or replace a plugin context's private hook storage (for example, ctx.hooks). A runtime may expose only ctx.register_hook(...), and even when multiple registrations are accepted their replacement/chaining semantics are runtime-specific.

For a wrapper that adds policy around a base hook:

  1. Build one merged kwargs mapping in the wrapper, resolving every wrapper-owned dependency (profile_name, surface, registry/audit paths, etc.). If the runtime supplies a key with None or an empty value, dict.setdefault() will not apply the fallback; assign the resolved value explicitly (merged['registry_path'] = resolved_registry_path) so required dependencies cannot be erased by a null runtime field.
  2. Pass that same merged mapping to both the wrapper's additional policy and the base hook. Computing defaults and then calling the base hook with the original kwargs silently discards the dependencies.
  3. Prefer an explicit composed callback registered through the public API; do not assume a second register_hook call preserves a previous callback.
  4. Add a regression fake that has register_hook and register_tool but deliberately lacks hooks, then invoke the registered hook without private injected parameters. Assert the normal policy path works rather than returning a configuration-path error.
  5. Prove the actual live runtime with a fresh process; local fake-context success alone does not establish hook ordering or discovery behavior.

See references/runtime-hook-composition-regression.md for the concrete policy-plugin failure sequence and canary matrix.

Controlled-launch preflight disagreement

For policy-plugin repairs that must use a controlled inspect_project / launch_specialist seam, treat a non-zero Git preflight inside the registered candidate root as a hard checkout-accessibility HOLD, even if a registry snapshot says the root is clean and writer-free. Never work around that by launching an ungoverned writer or deploying a previously staged payload. A staged manifest proves only the bytes present when it was created; compare the staged hashes for every required repair file—especially wrapper/bootstrap files—before considering it a candidate. If the active repair requires a later wrapper/config-propagation change absent from staging, preserve the stage and restore/reconcile source Git accessibility before retrying the one controlled launch. Record the exact Git subcommand and exit status as evidence.

Self-gated repair deadlock (policy plugin blocks its own deployment)

When the policy plugin that gates general execution is itself the broken component and its installed payload is stale:

  1. Expect every fresh profile session (default and specialist profiles) to resolve as the default profile and deny terminal/execute_code with default profile must use controlled supervisory tools. Proven live: a specialist-profile one-shot received the default-profile verdict.
  2. Agent-routed deployment is therefore impossible — including cron agent ticks, whose delegates inherit the same denied surface AND do not expose the controlled tools (tool_search finds none in subagent runtimes).
  3. Use the scheduler script channel instead: a no_agent: true cron job executes its script directly in the scheduler process, entirely outside the agent tool surface — the same mechanism apply-patches.sh and watchdog scripts already use. This is sanctioned infrastructure operation, not a guard bypass; keep the canary and rollback gates intact.
  4. Split the deployment into two phases with a review boundary: Phase A read-only (verify HEAD/dirty set, run the real test suite, snapshot per-profile installed hashes for rollback, stage an immutable merged payload with a hash manifest); Phase B (atomic per-profile replacement, fresh-process discovery canaries, gateway restart only after all pass, automatic rollback on any failure, exact result artifact DEPLOYED_AND_VERIFIED/ROLLED_BACK/HOLD).
  5. Never deploy a staged payload whose provenance predates the final candidate dirty set; re-stage from current candidate bytes.

Launch mechanics learned alongside this case:

  • launch_specialist / hermes --profile X chat -q "handoff:" one-shots exit in seconds without doing work — the spawned session treats the path as literal text. Pass the full brief inline as handoff text; inline-text one-shots do execute real work.
  • One-shot profile workers cap at the profile's max_turns (observed 40/40) and exit without writing result artifacts. Require artifact-first behavior or launch with --max-turns N.
  • Under the governed profile, write_file refuses new paths with path does not exist; the patch tool V4A *** Add File: mode creates files successfully.
  • One-shot no_agent cron jobs: use duration schedules ('1m', '30m'); after arming, verify last_run_at and the output artifacts rather than assuming the tick fired.

See references/self-gated-policy-plugin-deployment.md for the worked case and script skeleton.

Debugging checklist

When a plugin loads but does nothing:

  • Confirm:
  • Plugin is discovered from a supported directory or pip entry point
  • Profile gating resolves the intended live profile and enables only that profile
  • plugin.yaml uses provides_hooks and does not advertise hooks absent from the live registry
  • Hook functions are public and match registration names
  • A

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.