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

Event Hooks: Make Hermes React to What Happens

Event Hooks — Auto-Trigger at Key Moments — easy-to-understand guide based on official docs (updated 2026-08-09)

Event hooks: doorbells for the agent

Event Hooks: Make Hermes React to What Happens

If you’ve ever wanted Hermes to ping you when a task finishes, log every command, or send a webhook to your dashboard — event hooks are exactly what you need. Think of them as little “if this happens, do that” triggers that live inside your agent.

In this post, we’ll walk through the four hook systems Hermes offers, starting with the most flexible one: Gateway Event Hooks.

The Four Hook Systems at a Glance

System Where it runs Best for
Gateway hooks Gateway only (Telegram, Discord, etc.) Logging, alerts, webhooks
Plugin hooks CLI + Gateway Tool interception, metrics, guardrails
Shell hooks CLI + Gateway + Desktop/TUI/dashboard chat Drop-in scripts for blocking, formatting, context injection
Outbound webhooks CLI + Gateway Pushing events to external HTTP endpoints

The great news? If a hook fails, Hermes just logs the error and keeps going. Your agent won’t crash because a hook had a hiccup. Hooks aren’t all passive, though: directive/control hooks can change flow, transforms can replace content, and a shell pre_tool_call hook can block or fail closed.


Gateway Event Hooks: Your First Hook in 5 Minutes

Gateway hooks fire automatically when your agent runs on messaging platforms like Telegram, Discord, Slack, WhatsApp, or Teams. They don’t block the main pipeline — they just observe and react.

Step 1: Create the Hook Folder

Each hook is a directory under ~/.hermes/hooks/ with two files:

~/.hermes/hooks/
└── my-hook/
    ├── HOOK.yaml      # Tells Hermes which events to listen for
    └── handler.py     # Your Python code

Step 2: Declare Events in HOOK.yaml

name: my-hook
description: Log all agent activity to a file
events:
  - agent:start
  - agent:end
  - agent:step

The events list decides when your handler runs. You can subscribe to any combination — even wildcards like command:* to catch every slash command.

Step 3: Write Your Handler

import json
from datetime import datetime
from pathlib import Path

LOG_FILE = Path.home() / ".hermes" / "hooks" / "my-hook" / "activity.log"

async def handle(event_type: str, context: dict):
    """Called for each subscribed event. Must be named 'handle'."""
    entry = {
        "timestamp": datetime.now().isoformat(),
        "event": event_type,
        **context,
    }
    with open(LOG_FILE, "a") as f:
        f.write(json.dumps(entry) + "\n")

Handler rules to remember:

  • The function must be named handle
  • It receives event_type (string) and context (dict)
  • async def or regular def both work
  • Errors are caught and logged — never crash the agent

Key Events You’ll Want to Know

Here are the most useful events and what they give you:

Event When it fires What you get
gateway:startup Gateway starts List of active platforms
session:start New conversation begins Platform, user ID, session ID, session key
session:end Session ended (before reset) Platform, user ID, session key
session:reset User ran /new or /reset Platform, user ID, session key
session:compress Context compression completed for a session Platform, session ID, old session ID, in-place flag, compression count
agent:start Agent starts processing Platform, user ID, chat ID, thread ID, chat type, session ID, message (truncated to 500 chars)
agent:step Each tool-calling loop iteration Iteration number, tool names
agent:end Agent finishes Everything from agent:start plus the response
reaction:added An emoji reaction was added to a message the bot can see (Slack adapter currently) Platform, reaction, user ID, item user ID, item type, channel ID, message timestamp, team ID, event timestamp, raw event
reaction:removed An emoji reaction was removed from a message the bot can see Same shape as reaction:added
command:* Any slash command Command name and arguments

Pro tip: Use wildcards! A handler registered for command:* fires for command:model, command:reset, and all the rest — one subscription to monitor everything.


Real-World Example: Alert Yourself on Long Tasks

Want a Telegram message when your agent takes more than 10 steps? Here’s the whole hook:

# ~/.hermes/hooks/long-task-alert/HOOK.yaml
name: long-task-alert
description: Alert when agent is taking many steps
events:
  - agent:step
# ~/.hermes/hooks/long-task-alert/handler.py
import os
import httpx

THRESHOLD = 10
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
CHAT_ID = os.getenv("TELEGRAM_HOME_CHANNEL")

async def handle(event_type: str, context: dict):
    iteration = context.get("iteration", 0)
    if iteration == THRESHOLD and BOT_TOKEN and CHAT_ID:
        tools = ", ".join(context.get("tool_names", []))
        text = f"⚠️ Agent has been running for {iteration} steps. Last tools: {tools}"
        async with httpx.AsyncClient() as client:
            await client.post(
                f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
                json={"chat_id": CHAT_ID, "text": text},
            )

That’s it. Save the files, restart your gateway, and you’ll get pinged when things run long.


One More Tip: Threaded Replies

If you’re posting follow-up messages into a Telegram forum topic, include message_thread_id=int(thread_id) when chat_type == "forum" and thread_id is non-empty. Otherwise your message might land in the wrong place.


What’s Next?

Gateway hooks are just one of the four systems. In upcoming posts, we’ll dig into plugin hooks (for tool interception), shell hooks (for drop-in scripts), and outbound webhooks (for pushing events to CI, dashboards, or other agents).

For now, try building a simple logger or alert hook. It’s the fastest way to see how much visibility hooks give you into your agent’s inner workings.

📖 Official Docs

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