🤖HermesBlog
Hermes Feature Guides · Part 98/9/2026

Plugins: Make Hermes Truly Yours

Plugin System — Endless Extensions — easy-to-understand guide based on official docs (updated 2026-08-09)

Plugins: install apps on Hermes

Plugins: Make Hermes Truly Yours

If you’ve ever thought, “I wish Hermes could do this,” — plugins are your answer. They let you add custom tools, hooks, and integrations without touching Hermes’ core code. Think of them as LEGO bricks for your AI assistant: snap in a new capability, and the model can use it right away.

The Big Idea

Instead of modifying Hermes’ internals (which can break things and make upgrades painful), you drop a small folder into the right place. Hermes picks it up automatically, and your new tools appear alongside the built-in ones. The model can call them immediately — no restart required (well, one restart, but that’s it).

How It Works: A 30-Second Tour

Create a directory in ~/.hermes/plugins/ with a manifest file and some Python code:

~/.hermes/plugins/my-plugin/
├── plugin.yaml      # manifest — name, version, description
├── __init__.py      # register() — wires schemas to handlers
├── schemas.py       # tool schemas (what the LLM sees)
└── tools.py         # tool handlers (what runs when called)

That’s it. Start Hermes, and your tools are live.

Your First Plugin: Hello World

Let’s build something real. Here’s a complete plugin that adds a hello_world tool and logs every tool call:

~/.hermes/plugins/hello-world/plugin.yaml

name: hello-world
version: "1.0"
description: A minimal example plugin

~/.hermes/plugins/hello-world/__init__.py

"""Minimal Hermes plugin — registers a tool and a hook."""

import json


def register(ctx):
    # --- Tool: hello_world ---
    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):
        del kwargs
        name = params.get("name", "World")
        return json.dumps({"success": True, "greeting": f"Hello, {name}!"})

    ctx.register_tool(
        name="hello_world",
        toolset="hello_world",
        schema=schema,
        handler=handle_hello,
    )

    # --- Hook: log every tool call ---
    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)

Drop both files into ~/.hermes/plugins/hello-world/, restart Hermes, and ask the model to say hello. It works. The hook prints a log line after every tool invocation.

One tip: Put the tool’s description in schema["description"] — that’s what the model sees. The optional ctx.register_tool(description=...) is just internal registry metadata; if you omit it, it defaults to the schema description, but Hermes won’t copy it back into a schema that has none. Define the text once in the schema, and keep both in sync if you use both.

What Plugins Can Do

The ctx.* API is your toolbox. Here’s what you can build:

Capability How
Add tools ctx.register_tool(name=..., toolset=..., schema=..., handler=...)
Add hooks ctx.register_hook("post_tool_call", callback)
Add slash commands ctx.register_command(name, handler, description) — adds /name in CLI and gateway sessions
Dispatch tools from commands ctx.dispatch_tool(name, args) — invokes a registered tool with parent-agent context auto-wired
Add CLI commands ctx.register_cli_command(name, help, setup_fn, handler_fn) — adds hermes <plugin> <subcommand>
Inject messages ctx.inject_message(content, role="user", session_key=...)
Ship data files Path(__file__).parent / "data" / "file.yaml"
Bundle skills ctx.register_skill(name, path) — namespaced as plugin:skill, loaded via skill_view("plugin:skill")
Gate on env vars requires_env: [API_KEY] in plugin.yaml — prompted during hermes plugins install
Distribute via pip [project.entry-points."hermes_agent.plugins"]
Register a gateway platform ctx.register_platform(name, label, adapter_factory, check_fn, ...) — Discord, Telegram, IRC, and more
Register image/video generation ctx.register_image_gen_provider(provider) and ctx.register_video_gen_provider(provider)
Register a context engine ctx.register_context_engine(engine) — custom context-compression engines
Register a terminal backend ctx.register_terminal_environment_provider(provider) — cloud sandbox execution
Route approval prompts ctx.register_approval_transport(name, present_fn) — custom human-approval flows
Register a memory backend Subclass MemoryProvider in plugins/memory/<name>/__init__.py (separate discovery system)
Run host-owned LLM calls ctx.llm.complete(...) / ctx.llm.complete_structured(...) — borrow the user’s active model + auth
Call MCP tools ctx.call_mcp(server, tool, arguments, timeout=30)
Register an inference backend register_provider(ProviderProfile(...)) in plugins/model-providers/<name>/__init__.py

Plugins can also ship from several sources: bundled (<repo>/plugins/), user (~/.hermes/plugins/), project, pip entry points, and NixOS declarative installs via services.hermes-agent.extraPlugins / extraPythonPackages.

Plugins Are Opt-In

General plugins and user-installed backends are disabled by default. Discovery still finds them (so they show up in hermes plugins and /plugins), but nothing with hooks or tools loads until you add the plugin’s name to plugins.enabled in ~/.hermes/config.yaml. That keeps third-party code from running without your explicit consent.

hermes plugins                    # interactive toggle (space to check/uncheck)
hermes plugins enable <name>      # add to allow-list
hermes plugins disable <name>     # remove from allow-list + add to disabled

After hermes plugins install owner/repo, you’re asked Enable 'name' now? [y/N] — it defaults to no. For scripted installs, skip the prompt with --enable or --no-enable. Bundled “always-works” infrastructure (bundled platform plugins, bundled backends, memory providers, context engines, model providers) bypasses the allow-list and is activated through config.yaml instead.

Safety First

Project-local plugins under ./.hermes/plugins/ are disabled by default. That’s intentional — you don’t want random repos running arbitrary code on your machine. Enable them only for trusted repositories:

export HERMES_ENABLE_PROJECT_PLUGINS=true

Pinning and Private Repositories

For a reproducible install, pin a full immutable commit (tags, branches, and abbreviated SHAs are not accepted):

hermes plugins install owner/repo --ref 0123456789abcdef0123456789abcdef01234567

Hermes checks out the commit detached, verifies that HEAD exactly matches the requested SHA, and records the canonical source, installed revision, and pin status in the current profile. hermes plugins update refuses to move a pinned plugin; choose a new exact commit explicitly with hermes plugins install <source> --force --ref <new-commit>. The same pin is available in Hermes Desktop under Skills → Plugins → Install from Git (a Pin to commit field that takes the full 40-character SHA), and the plugins list shows a pinned @ <sha8> badge on every pinned install. hermes plugins list prints the pin in its Source column (git pinned@<sha8>).

hermes plugins install clones non-interactively (it never prompts for a username or password), so a private repo needs a credential Hermes can find on its own. Every clone, pinned --ref fetch, and hermes plugins update pull is attempted anonymously first — public repos never see your credential, so a stale or revoked token can’t break a public install. Only when the remote refuses anonymous access does Hermes look for a credential. For an https:// source it tries, in order: GITHUB_TOKEN or GH_TOKEN from your .env (GitHub hosts only); the gh CLI’s login (gh auth login), GitHub hosts only; then your git credential helper (git credential fill) for that host — which works for GitLab, Bitbucket and self-hosted servers if a credential is already stored. The credential is sent as a one-shot HTTP header for that install or update and is never written into the plugin’s .git/config or the install metadata. SSH sources (git@host:owner/repo.git) authenticate through your ssh-agent as before. The same resolution applies to hermes plugins update, catalog MCP installs from git, and profile distributions fetched from a git URL.

The Bottom Line

Plugins are the cleanest way to extend Hermes. No core modifications, no merge conflicts, no “it worked on my machine” drama. Just drop a folder, restart, and go.

Want the full walkthrough? Check out the Build a Hermes Plugin guide — it has a complete working example you can copy and adapt.

Your Hermes, your rules. Happy building!

📖 Official Docs

This article is based on the official Hermes Agent documentation:Official docs › user-guide/features/plugins