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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
AI Step-by-Step
Sharing AI knowledge, practical implementation records, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
