Appearance
Plugins & hooks
When the built-in toolset and MCP servers aren't enough, the plugin system lets you add your own tools, slash commands, and lifecycle hooks without touching Hermes core. Everything runs locally, so this is the clean way to wire the agent into your own scripts and machines.
A minimal plugin
Drop a directory into ~/.hermes/plugins/ with a manifest and Python code:
~/.hermes/plugins/hello-world/
├── plugin.yaml # manifest
└── __init__.py # register() wires schemas to handlersyaml
# plugin.yaml
name: hello-world
version: "1.0"
description: A minimal example pluginpython
# __init__.py
import json
def register(ctx):
schema = {
"name": "hello_world",
"description": "Returns a friendly greeting for the given name.",
"parameters": {
"type": "object",
"properties": {"name": {"type": "string", "description": "Name to greet"}},
"required": ["name"],
},
}
def handle_hello(params, **kwargs):
return json.dumps({"success": True, "greeting": f"Hello, {params.get('name', 'World')}!"})
ctx.register_tool(name="hello_world", toolset="hello_world",
schema=schema, handler=handle_hello,
description="Return a friendly greeting.")
def on_tool_call(tool_name, params, result):
print(f"[hello-world] tool called: {tool_name}")
ctx.register_hook("post_tool_call", on_tool_call)Enable it and restart, and the model can call hello_world immediately.
What a plugin can do
Inside register(ctx) you have a context object that can:
ctx.register_tool(...): add a tool the model can call.ctx.register_command(...): add a/nameslash command in CLI and gateway.ctx.register_cli_command(...): add ahermes <plugin> <subcommand>.ctx.register_hook(...): run code at lifecycle events.ctx.register_skill(...): ship a bundled skill.ctx.inject_message(...): feed a message into the active CLI conversation (useful for webhook receivers).
It can also register whole backends (gateway platforms, image-gen, memory providers, context engines, model providers), but those are deeper developer-guide territory.
Plugins are opt-in
For safety, general user plugins are disabled by default. Hermes discovers them but loads nothing until you enable it:
yaml
# ~/.hermes/config.yaml
plugins:
enabled:
- hello-world
- disk-cleanup
disabled: # deny-list always wins
- noisy-pluginbash
hermes plugins # interactive toggle UI
hermes plugins list # enabled / disabled / not-enabled
hermes plugins install user/repo --enable
hermes plugins enable hello-worldProject-local plugins under ./.hermes/plugins/ are off unless you set HERMES_ENABLE_PROJECT_PLUGINS=true, enable them only for repos you trust.
Hooks
Hooks let plugins run code at key moments. Available events:
| Hook | Fires when |
|---|---|
pre_tool_call / post_tool_call | Before / after any tool runs |
pre_llm_call / post_llm_call | Once per turn, around the LLM loop (pre_llm_call can inject context) |
on_session_start / on_session_end | Session lifecycle |
subagent_stop | After a delegated child finishes |
pre_gateway_dispatch | Gateway received a message, before auth/dispatch |
Lighter-weight extension points
Not everything needs Python. Two config-driven options cover a lot of ground:
Quick commands (exec / alias) run a shell command or rewrite to a slash command, no LLM call, no tokens:
yaml
# ~/.hermes/config.yaml
quick_commands:
status:
type: exec
command: systemctl status hermes-agent
gpu:
type: exec
command: nvidia-smi --query-gpu=utilization.gpu,memory.used --format=csv,noheader
restart:
type: alias
target: /gateway restartThen type /status, /gpu, or /restart anywhere. Great on a local box for checking GPU load mid-session.
Shell hooks run a shell command on agent/gateway events (notifications, audit logs) and are declared under hooks: in config.yaml.
Where to go deeper
For full handler contracts, schema format, and a complete worked example, see the official Build a Hermes Plugin guide and the Hooks reference.