Hermes Plugin Development
Goal
Design, implement, and validate Hermes general plugins without drifting into provider-plugin internals.
When To Use
Use this skill for:
- New Hermes plugins under
~/.hermes/plugins/<name>/ or .hermes/plugins/<name>/
- Existing plugin edits involving
plugin.yaml, __init__.py, schemas.py, or tools.py
- Adding Hermes tools, hooks, slash commands, CLI commands, or bundled skills
- Debugging discovery, missing env gating, or opt-in enablement problems
Do not use this skill as the primary guide for memory-provider or context-engine plugins. Mention them only as specialized follow-on work.
Plugin Shape
Standard Hermes general plugin layout:
~/.hermes/plugins/my-plugin/
├── plugin.yaml
├── __init__.py
├── schemas.py
└── tools.py
plugin.yaml: plugin identity plus declared capabilities
schemas.py: tool schemas the model sees
tools.py: handlers that do the work
__init__.py: register(ctx) wiring for tools, hooks, commands, or skills
Project-local plugins in .hermes/plugins/ are disabled by default. They only load when Hermes starts with HERMES_ENABLE_PROJECT_PLUGINS=true.
Workflow
- Decide the plugin surface first.
- Tools:
ctx.register_tool
- Hooks:
ctx.register_hook
- Slash commands:
ctx.register_command
- CLI commands:
ctx.register_cli_command
- Bundled skills:
ctx.register_skill
- Write
plugin.yaml.
- Include
name, version, and description.
- Add
provides_tools or provides_hooks when it improves clarity.
- Add
requires_env only when the plugin truly depends on environment variables.
- Define schemas in
schemas.py.
- Schemas are model-facing contracts, so descriptions must say when to use the tool and what each field means.
- Keep the parameter object explicit, with clear
properties and required.
- Implement handlers in
tools.py.
- Accept
args, **kwargs.
- Return JSON strings, including on error paths.
- Catch exceptions and turn them into error JSON instead of crashing the tool call.
- Register everything in
__init__.py.
- Wire schema to handler with
register(ctx).
- Add hooks or commands only after the base tool flow is correct.
- Validate discovery and enablement.
- Discovery is not enough: Hermes plugins are opt-in.
- Enable the plugin in
plugins.enabled or via hermes plugins enable <name>.
- Use
/plugins in a running session to confirm loaded state.
Minimal Skeleton
plugin.yaml
name: hello-world
version: "1.0"
description: Minimal Hermes plugin with one greeting tool
provides_tools:
- hello_world
schemas.py
HELLO_WORLD_SCHEMA = {
"name": "hello_world",
"description": "Return a friendly greeting for the provided name.",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name to greet",
}
},
"required": ["name"],
},
}
tools.py
import json
def hello_world(args, **kwargs):
try:
name = args.get("name", "World")
return json.dumps({"message": f"Hello, {name}!"})
except Exception as e:
return json.dumps({"error": str(e)})
__init__.py
from .schemas import HELLO_WORLD_SCHEMA
from .tools import hello_world
def register(ctx):
ctx.register_tool("hello_world", HELLO_WORLD_SCHEMA, hello_world)
Guardrails
- Handlers must return JSON strings, not Python dicts.
- Handlers should accept
args, **kwargs for forward compatibility.
- Catch exceptions in handlers and return structured error JSON.
- Tool descriptions must be specific enough that the model knows when to call them.
- Project-local plugins need
HERMES_ENABLE_PROJECT_PLUGINS=true.
- Discovered plugins stay inactive until explicitly enabled.
Optional Extensions
- Hooks: use
ctx.register_hook for lifecycle events like pre_tool_call, post_tool_call, pre_llm_call, post_llm_call, on_session_start, and on_session_end.
- Slash commands: use
ctx.register_command(name, handler, description) when the feature should appear in chat sessions as /name.
- CLI commands: use
ctx.register_cli_command(...) when the feature should add hermes <plugin> <subcommand> behavior.
- Bundled skills: use
ctx.register_skill(name, path) when the plugin should ship promptable skills namespaced as plugin:skill.
- Env gating: use
requires_env in plugin.yaml for API keys or similar dependencies; Hermes can prompt for missing values during plugin install.
- Distribution: for packaged plugins, expose an entry point under
project.entry-points."hermes_agent.plugins" in pyproject.toml.
Validation Checklist
- Hermes starts without plugin import or registration errors.
hermes plugins list shows the plugin as discovered.
- The plugin is explicitly enabled, not merely installed.
- The expected tool or command appears and executes successfully.
/plugins in a running session shows the plugin as loaded.
- Missing
requires_env values disable the plugin cleanly instead of crashing it.
Common Mistakes
- Returning a dict from a handler instead of
json.dumps(...)
- Omitting
**kwargs from the handler signature
- Letting exceptions escape from the handler
- Writing vague schema descriptions like
"Does stuff"
- Assuming a plugin is active because Hermes discovered it
1---2name: hermes-plugin-development3description: Create or update Hermes general plugins with `plugin.yaml`, `register(ctx)`, tool schemas, handlers, hooks, slash commands, CLI commands, and discovery or enablement debugging. Use when asked to build a Hermes plugin, write `plugin.yaml`, implement `register(ctx)`, add Hermes tools or hooks, or troubleshoot plugin loading and opt-in enablement.4---56# Hermes Plugin Development78## Goal910Design, implement, and validate Hermes general plugins without drifting into provider-plugin internals.1112## When To Use1314Use this skill for:1516- New Hermes plugins under `~/.hermes/plugins/<name>/` or `.hermes/plugins/<name>/`17- Existing plugin edits involving `plugin.yaml`, `__init__.py`, `schemas.py`, or `tools.py`18- Adding Hermes tools, hooks, slash commands, CLI commands, or bundled skills19- Debugging discovery, missing env gating, or opt-in enablement problems2021Do not use this skill as the primary guide for memory-provider or context-engine plugins. Mention them only as specialized follow-on work.2223## Plugin Shape2425Standard Hermes general plugin layout:2627```text28~/.hermes/plugins/my-plugin/29├── plugin.yaml30├── __init__.py31├── schemas.py32└── tools.py33```3435- `plugin.yaml`: plugin identity plus declared capabilities36- `schemas.py`: tool schemas the model sees37- `tools.py`: handlers that do the work38- `__init__.py`: `register(ctx)` wiring for tools, hooks, commands, or skills3940Project-local plugins in `.hermes/plugins/` are disabled by default. They only load when Hermes starts with `HERMES_ENABLE_PROJECT_PLUGINS=true`.4142## Workflow43441. Decide the plugin surface first.45- Tools: `ctx.register_tool`46- Hooks: `ctx.register_hook`47- Slash commands: `ctx.register_command`48- CLI commands: `ctx.register_cli_command`49- Bundled skills: `ctx.register_skill`50512. Write `plugin.yaml`.52- Include `name`, `version`, and `description`.53- Add `provides_tools` or `provides_hooks` when it improves clarity.54- Add `requires_env` only when the plugin truly depends on environment variables.55563. Define schemas in `schemas.py`.57- Schemas are model-facing contracts, so descriptions must say when to use the tool and what each field means.58- Keep the parameter object explicit, with clear `properties` and `required`.59604. Implement handlers in `tools.py`.61- Accept `args, **kwargs`.62- Return JSON strings, including on error paths.63- Catch exceptions and turn them into error JSON instead of crashing the tool call.64655. Register everything in `__init__.py`.66- Wire schema to handler with `register(ctx)`.67- Add hooks or commands only after the base tool flow is correct.68696. Validate discovery and enablement.70- Discovery is not enough: Hermes plugins are opt-in.71- Enable the plugin in `plugins.enabled` or via `hermes plugins enable <name>`.72- Use `/plugins` in a running session to confirm loaded state.7374## Minimal Skeleton7576`plugin.yaml`7778```yaml79name: hello-world80version: "1.0"81description: Minimal Hermes plugin with one greeting tool82provides_tools:83 - hello_world84```8586`schemas.py`8788```python89HELLO_WORLD_SCHEMA = {90 "name": "hello_world",91 "description": "Return a friendly greeting for the provided name.",92 "parameters": {93 "type": "object",94 "properties": {95 "name": {96 "type": "string",97 "description": "Name to greet",98 }99 },100 "required": ["name"],101 },102}103```104105`tools.py`106107```python108import json109110111def hello_world(args, **kwargs):112 try:113 name = args.get("name", "World")114 return json.dumps({"message": f"Hello, {name}!"})115 except Exception as e:116 return json.dumps({"error": str(e)})117```118119`__init__.py`120121```python122from .schemas import HELLO_WORLD_SCHEMA123from .tools import hello_world124125126def register(ctx):127 ctx.register_tool("hello_world", HELLO_WORLD_SCHEMA, hello_world)128```129130## Guardrails131132- Handlers must return JSON strings, not Python dicts.133- Handlers should accept `args, **kwargs` for forward compatibility.134- Catch exceptions in handlers and return structured error JSON.135- Tool descriptions must be specific enough that the model knows when to call them.136- Project-local plugins need `HERMES_ENABLE_PROJECT_PLUGINS=true`.137- Discovered plugins stay inactive until explicitly enabled.138139## Optional Extensions140141- Hooks: use `ctx.register_hook` for lifecycle events like `pre_tool_call`, `post_tool_call`, `pre_llm_call`, `post_llm_call`, `on_session_start`, and `on_session_end`.142- Slash commands: use `ctx.register_command(name, handler, description)` when the feature should appear in chat sessions as `/name`.143- CLI commands: use `ctx.register_cli_command(...)` when the feature should add `hermes <plugin> <subcommand>` behavior.144- Bundled skills: use `ctx.register_skill(name, path)` when the plugin should ship promptable skills namespaced as `plugin:skill`.145- Env gating: use `requires_env` in `plugin.yaml` for API keys or similar dependencies; Hermes can prompt for missing values during plugin install.146- Distribution: for packaged plugins, expose an entry point under `project.entry-points."hermes_agent.plugins"` in `pyproject.toml`.147148## Validation Checklist149150- Hermes starts without plugin import or registration errors.151- `hermes plugins list` shows the plugin as discovered.152- The plugin is explicitly enabled, not merely installed.153- The expected tool or command appears and executes successfully.154- `/plugins` in a running session shows the plugin as loaded.155- Missing `requires_env` values disable the plugin cleanly instead of crashing it.156157## Common Mistakes158159- Returning a dict from a handler instead of `json.dumps(...)`160- Omitting `**kwargs` from the handler signature161- Letting exceptions escape from the handler162- Writing vague schema descriptions like `"Does stuff"`163- Assuming a plugin is active because Hermes discovered it