Implementing a Harness in Hermes: Tool Registry, Plugin System, and Context Engine

The article dissects Hermes’s extension layer, showing how the Tool Registry converts functions into runtime assets, how AST‑based auto‑discovery and availability checks keep tool lists accurate, and how the unified dispatch, Plugin System, Hook System, and replaceable Context Engine together form a robust, governable agent platform.

AI Step-by-Step
AI Step-by-Step
AI Step-by-Step
Implementing a Harness in Hermes: Tool Registry, Plugin System, and Context Engine

Overview

While many focus on the MCP when evaluating an Agent's extensibility, Hermes places equal emphasis on internal capabilities: tool registration, tool dispatch, plugin loading, lifecycle hooks, and a replaceable Context Engine.

1. Tool Registry Turns Functions into Runtime Assets

A plain function is not an Agent capability until its name, description, parameter schema, toolset, environment requirements, availability check, async flag, and result budget are registered. Hermes’s built‑in tools use import‑time registration by calling registry.register() at module top level.

def list_files(path: str = ".") -> dict:
    return {"files": sorted(os.listdir(path))}

registry.register(
    name="list_files",
    toolset="filesystem",
    schema={
        "type": "function",
        "function": {
            "name": "list_files",
            "description": "List files in a workspace path.",
            "parameters": {
                "type": "object",
                "properties": {"path": {"type": "string"}},
            },
        },
    },
    handler=list_files,
    check_fn=lambda: os.path.isdir(os.getcwd()),
)

Registration collects all metadata into a ToolEntry, enabling unified discovery, filtering, dispatch, and auditing. Over 60 registration points already exist in the tools directory.

name : stable identifier used by the model.

toolset : groups tools (e.g., filesystem, browser, RL, skills).

schema : generates a model‑readable function definition.

handler : the actual implementation.

check_fn : hides the tool when dependencies are missing.

requires_env : supplies missing environment variable hints.

2. AST‑Based Auto‑Discovery Avoids Mistaking Helpers for Tools

Hermes scans tools/*.py files and uses an AST to detect top‑level calls to registry.register(...). Only modules that contain such a call are imported.

This prevents side‑effects from helper modules (constants, config, backend wrappers) from being executed during discovery.

Discovery proceeds in four steps:

Scan the tools directory, excluding registry files, init files, and MCP entry points.

Use AST to check for top‑level registry.register(...) calls.

Import the matching modules, triggering registration.

Store ToolEntry objects for later toolset resolution and schema generation.

Because the discovery logic lives in each tool module, the central list does not grow brittle as the number of tools increases.

3. Schema Generation with Availability Checks Reduces Hallucinations

Before exposing tools to the model, Hermes resolves enabled toolsets and runs each tool’s check_fn. Tools that fail the check are omitted from the model’s tools list, preventing the model from attempting calls that cannot execute.

def browser_available() -> bool:
    return shutil.which("chromium") is not None

registry.register(
    name="browser_click",
    toolset="browser",
    schema=browser_click_schema,
    handler=browser_click,
    check_fn=browser_available,
    requires_env=["BROWSER_PROFILE"],
)

Hermes also patches schemas at runtime. For example, the execute_code tool’s description is updated to list only the sandbox tools that are actually available.

4. Dispatch Centralizes Execution, Async Bridging, and Error Wrapping

When the model returns a tool call, special‑purpose tools (e.g., todo, memory, delegate_task, Context Engine tools) are handled first. Ordinary tools flow through handle_function_call and are dispatched via registry.dispatch to their handlers.

Dispatch provides two key guarantees:

Asynchronous handlers are run through a unified bridge, so callers need not manage event loops.

Exceptions are caught and wrapped into a consistent JSON error format, giving the model a stable failure representation.

Hooks surround the execution chain: pre_tool_call for strategy checks, post_tool_call for timing and result logging, and transform_tool_result for normalizing output before it reaches the message history.

5. Plugin System Integrates Third‑Party Capabilities

Plugins register tools, hooks, CLI commands, read‑only skills, image providers, or even a Context Engine. Registration always ends up in the global Tool Registry via PluginContext.register_tool().

Plugins can originate from four sources:

bundled plugins : built‑in back‑ends or examples shipped with Hermes.

user plugins : located under ~/.hermes/plugins, enabled via configuration.

project plugins : under .hermes/plugins, require an explicit project‑level switch.

pip entry points : discovered via hermes_agent.plugins entry points and loaded according to the enabled manifest.

Loading follows a conservative opt‑in model; later sources can override earlier ones after explicit enablement, preserving auditability.

{
  "name": "workspace-policy",
  "version": "0.1.0",
  "entry": "plugin.py",
  "description": "Project-level tool policy and audit hooks",
  "permissions": ["tools", "hooks"]
}

def setup(ctx):
    ctx.register_tool(
        name="workspace_status",
        toolset="project",
        schema=status_schema,
        handler=workspace_status,
    )
    ctx.register_hook("pre_tool_call", require_workspace_policy)

6. Hook System Places Extension Points in the Runtime Lifecycle

Hooks cover tool calls, result transformation, LLM calls, API requests, session start/end, session reset, sub‑agent stop, and gateway message dispatch.

Typical uses include:

Security policies : block high‑risk tool calls before execution.

Observability & audit : record tool latency, failure rates, and session metadata.

Context enhancement : inject temporary recall or project status before LLM calls.

Gateway governance : rewrite, skip, or allow external messages before dispatch.

Each callback runs inside its own exception guard, ensuring a single faulty plugin does not crash the Agent loop (fail‑open design).

7. Context Engine Makes Context Management a Replaceable Strategy

The default engine, ContextCompressor, decides when to compress, how to rewrite messages, and updates token state. Third‑party engines can replace it to provide knowledge‑graph indexing, lossless storage, or custom retrieval tools.

Configuration selects a single engine via context.engine. The system first looks for plugins/context_engine/<name>, then for engines registered through the normal plugin system, and finally falls back to the built‑in compressor.

Engines may also expose agent‑callable tools that bypass the regular registry and invoke handle_tool_call directly, because they are tightly bound to the engine’s internal state.

class GraphContextEngine:
    name = "graph"

    def should_compress(self, messages, token_state) -> bool:
        return token_state.used_ratio > 0.8

    def compress(self, messages, token_state):
        graph = build_project_graph(messages)
        return rewrite_messages_with_graph_summary(messages, graph)

    def get_tools(self):
        return [search_context_graph_schema]

    async def handle_tool_call(self, name, arguments):
        return await search_context_graph(arguments["query"])

8. MCP Remains the External Standard Interface

MCP lets Hermes connect to external MCP servers and import tools via a uniform protocol. Imported tools still pass through the internal registry for naming, schema generation, dynamic refresh, conflict handling, and dispatch, preserving Hermes’s governance model.

9. Trade‑offs and Governance Considerations

The combination of Registry, Plugin, Hook, and Context Engine yields powerful extensibility but raises runtime complexity:

More plugins increase discovery, manifest parsing, and availability‑check costs, requiring caching and explicit enablement.

Hooks should remain lightweight; heavy network calls inside hooks can stall the main tool/LLM pipeline.

Testing a Context Engine is harder than testing a simple tool because it manipulates session state and requires clear fallback paths.

When MCP, plugin tools, and built‑in tools share the same governance chain, naming conflicts, permission boundaries, and result budgets must be predefined.

Consequently, this architecture suits long‑lived, evolving Agent platforms rather than single‑task scripts.

Summary Snapshot

Tool Registry unifies schema, handler, toolset, availability checks, and execution entry.

AST‑based auto‑discovery reduces central list maintenance.

Plugin System brings third‑party tools, hooks, commands, and providers into the same backbone.

Hook System enables governance at tool, LLM, session, and gateway stages.

Context Engine makes context management a pluggable, replaceable strategy.

The price is higher runtime complexity: plugin startup cost, hook latency, naming conflicts, and context‑engine testing.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Agent FrameworkHermesPlugin SystemAuto-discoveryTool RegistryContext Engine
AI Step-by-Step
Written by

AI Step-by-Step

Sharing AI knowledge, practical implementation records, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.