# Understanding the Event Hooks System in Hermes Agent: A Complete Guide

> Explore Hermes Agent's event hooks system to extend functionality with custom Python code. Learn to create your own hooks in ~/.hermes/hooks/ with this complete guide.

- Repository: [Nous Research/hermes-agent](https://github.com/NousResearch/hermes-agent)
- Tags: how-to-guide
- Published: 2026-03-09

---

**The event hooks system in Hermes Agent is a lightweight Python subsystem that executes custom code at specific lifecycle moments, allowing you to extend functionality by placing a [`HOOK.yaml`](https://github.com/NousResearch/hermes-agent/blob/main/HOOK.yaml) manifest and [`handler.py`](https://github.com/NousResearch/hermes-agent/blob/main/handler.py) file in `~/.hermes/hooks/`.**

Hermes Agent, developed by NousResearch, ships with a tiny but powerful **event hooks system** that lets developers inject arbitrary Python logic into the gateway and agent lifecycles. Whether you need to log tool calls to an external service, monitor slash commands, or initialize resources on startup, this system provides a safe, isolated mechanism without modifying core source code.

## How the Event Hooks System in Hermes Agent Works

The architecture centers on a single registry class that discovers user-provided modules and emits events asynchronously. All core logic resides in [`gateway/hooks.py`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/hooks.py).

### HookRegistry and Discovery

The `HookRegistry` class handles the entire lifecycle of hook management. During gateway startup, the registry calls `discover_and_load()`, which scans the directory defined by `HOOKS_DIR` (hardcoded to `Path(os.path.expanduser("~/.hermes/hooks"))` at line 30 of [`gateway/hooks.py`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/hooks.py)).

For each subdirectory in `~/.hermes/hooks/`, the loader expects two files:

- **[`HOOK.yaml`](https://github.com/NousResearch/hermes-agent/blob/main/HOOK.yaml)** – A manifest declaring the hook name, description, and the list of events to subscribe to.
- **[`handler.py`](https://github.com/NousResearch/hermes-agent/blob/main/handler.py)** – A Python module containing a top-level `handle(event_type, context)` callable.

Valid handlers are cached in the `_handlers` dictionary keyed by event names, supporting both exact matches and wildcard patterns like `command:*`.

### Event Emission

When significant lifecycle moments occur, the gateway calls `await hooks.emit(event_type, context)` (implemented at lines 118-150 in [`gateway/hooks.py`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/hooks.py)). This method:

1. Builds a handler list including exact event matches and wildcard listeners.
2. Executes handlers sequentially, supporting both synchronous functions and `async` coroutines.
3. Catches and logs all exceptions with a `[hooks]` prefix, ensuring faulty hooks never crash the main pipeline.

The system supports eight distinct event types declared in [`gateway/hooks.py`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/hooks.py) (lines 9-16):

- `gateway:startup` – Gateway process initialization
- `session:start` – New session creation
- `session:reset` – User executes `/new` or `/reset`
- `agent:start` – Agent begins processing a message
- `agent:step` – Each turn of the tool-calling loop
- `agent:end` – Agent finishes processing
- `command:*` – Wildcard matching any slash command

## Creating Custom Hooks in ~/.hermes/hooks/

Building a custom hook requires three components: a directory, a manifest, and a handler module.

### Step 1: Create the Hook Directory

Create a new subdirectory under `~/.hermes/hooks/` using a descriptive name for your extension. The directory name becomes the hook identifier.

```bash
mkdir -p ~/.hermes/hooks/my-logger

```

### Step 2: Define the HOOK.yaml Manifest

Inside your hook directory, create a [`HOOK.yaml`](https://github.com/NousResearch/hermes-agent/blob/main/HOOK.yaml) file declaring the metadata and event subscriptions. The `name` and `events` keys are mandatory.

```yaml

# ~/.hermes/hooks/my-logger/HOOK.yaml

name: my-logger
description: Log every tool-call step to a remote service
events:
  - agent:step

```

You can subscribe to multiple events or use wildcards like `command:*` to capture all slash commands.

### Step 3: Implement the handler.py File

Create a [`handler.py`](https://github.com/NousResearch/hermes-agent/blob/main/handler.py) file exporting a `handle(event_type, context)` function. This function receives the event name as a string and a context dictionary containing event-specific data. The function can be synchronous or asynchronous.

```python

# ~/.hermes/hooks/my-logger/handler.py

import json
import httpx

async def handle(event_type, context):
    """Post agent step details to an external monitoring endpoint."""
    payload = {
        "event": event_type,
        "session": context.get("session_id"),
        "tool": context.get("tool_name"),
        "iteration": context.get("iteration"),
    }
    async with httpx.AsyncClient() as client:
        await client.post("https://example.com/agent-logs", json=payload)

```

For `agent:step` events, the context dictionary includes keys like `task_id`, `tool_name`, and `iteration`. For `session:start`, it includes `session_id` and `config`.

### Step 4: Reload and Activate

The `HookRegistry` discovers hooks only during the discovery phase, typically at gateway startup. To activate your new hook:

```bash
hermes gateway restart

```

Alternatively, if running a development instance, you can trigger `discover_and_load()` programmatically, though a full restart is the standard workflow for production deployments.

## Practical Examples of Custom Event Hooks

Below are three complete, runnable examples demonstrating different hook patterns.

### Example 1: Minimal Session Greeting

This hook prints a console message whenever a new session begins.

```yaml

# ~/.hermes/hooks/hello/HOOK.yaml

name: hello
description: Greet when a new session begins
events:
  - session:start

```

```python

# ~/.hermes/hooks/hello/handler.py

def handle(event_type, context):
    print("[hooks] New session started – welcome!")

```

### Example 2: Local Tool-Call Logger

This synchronous hook appends every tool invocation to a local log file.

```yaml

# ~/.hermes/hooks/tool-recorder/HOOK.yaml

name: tool-recorder
description: Append tool usage to ~/.hermes/tool.log
events:
  - agent:step

```

```python

# ~/.hermes/hooks/tool-recorder/handler.py

import os
from datetime import datetime

def handle(event_type, context):
    log_path = os.path.expanduser("~/.hermes/tool.log")
    entry = f"{datetime.utcnow().isoformat()} | {context.get('tool_name')} | {context.get('iteration')}\n"
    with open(log_path, "a", encoding="utf-8") as f:
        f.write(entry)

```

### Example 3: Asynchronous Command Monitor

This async hook forwards all slash commands to a monitoring service using HTTP.

```yaml

# ~/.hermes/hooks/command-watcher/HOOK.yaml

name: command-watcher
description: Forward all slash commands to a monitoring service
events:
  - command:*

```

```python

# ~/.hermes/hooks/command-watcher/handler.py

import httpx

async def handle(event_type, context):
    async with httpx.AsyncClient() as client:
        await client.post(
            "https://monitor.example.com/command",
            json={"event": event_type, "data": context},
        )

```

## Safety Guarantees and Error Handling

The **event hooks system in Hermes Agent** prioritizes stability through three core mechanisms:

- **Error Isolation** – Every hook executes inside a try-except block. If a handler raises an exception, the registry logs it with a `[hooks]` prefix and continues processing remaining hooks. Faulty code never crashes the gateway or aborts the agent pipeline.
- **Wildcard Support** – The `emit()` method builds handler lists using both exact string matches and glob-style wildcards. Declaring `command:*` in your [`HOOK.yaml`](https://github.com/NousResearch/hermes-agent/blob/main/HOOK.yaml) captures every slash command without enumerating them individually.
- **Async Compatibility** – The registry handles both synchronous functions and `async` coroutines transparently. `emit()` awaits async handlers but never blocks the event loop for synchronous ones.

## Summary

The **event hooks system in Hermes Agent** provides a robust extension point for the NousResearch gateway. Key takeaways include:

- Hooks reside in `~/.hermes/hooks/` and require a [`HOOK.yaml`](https://github.com/NousResearch/hermes-agent/blob/main/HOOK.yaml) manifest plus a [`handler.py`](https://github.com/NousResearch/hermes-agent/blob/main/handler.py) module.
- The `HookRegistry` class in [`gateway/hooks.py`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/hooks.py) handles discovery via `discover_and_load()` and emission via `emit()`.
- Supported events include `gateway:startup`, `session:start`, `agent:step`, and wildcard patterns like `command:*`.
- Handlers receive `(event_type, context)` and may be synchronous or asynchronous.
- Errors are isolated and logged, ensuring hook failures never disrupt core operations.

## Frequently Asked Questions

### What file structure is required for a custom hook in Hermes Agent?

You must create a directory under `~/.hermes/hooks/` containing exactly two files: a [`HOOK.yaml`](https://github.com/NousResearch/hermes-agent/blob/main/HOOK.yaml) manifest describing the hook name and subscribed events, and a [`handler.py`](https://github.com/NousResearch/hermes-agent/blob/main/handler.py) Python module exporting a `handle(event_type, context)` function. The directory name typically matches the hook name for clarity.

### Can I use async functions in my hook handlers?

Yes. The `HookRegistry` in [`gateway/hooks.py`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/hooks.py) automatically detects whether your `handle` function is a coroutine. If it is async, the registry awaits it during `emit()`; if synchronous, it calls it directly. Both styles can coexist in the same hook directory.

### What happens if my custom hook raises an exception?

The event hooks system catches all exceptions inside a try-except block during handler execution. It logs the traceback with a `[hooks]` prefix to stderr or the configured logger, then continues with the next handler. Your hook failure never propagates to the gateway or interrupts the agent session.

### How do I listen to every slash command without listing them individually?

Use the wildcard event pattern `command:*` in your [`HOOK.yaml`](https://github.com/NousResearch/hermes-agent/blob/main/HOOK.yaml) events list. The `emit()` method in [`gateway/hooks.py`](https://github.com/NousResearch/hermes-agent/blob/main/gateway/hooks.py) expands wildcards when building the handler list, so a hook subscribed to `command:*` receives events for `/new`, `/reset`, and any other slash command automatically.