Hermes Agent event hooks explained

Choose between gateway hooks, shell hooks, plugin hooks and outbound webhooks, and set up a hook that logs, blocks or notifies without surprising you later.

Docs-backed3 min read · Reviewed Oct 20, 2026

Four hook systems, four jobs

Hooks let you run code at fixed points in Hermes' lifecycle: when a session starts, before a tool runs, after the model replies, when a subagent finishes. Hermes has four separate systems for this, and choosing the right one is most of the work:

  • Gateway hooks: a HOOK.yaml and a handler.py in a folder under ~/.hermes/hooks/. They run only in the messaging gateway and suit logging, alerts and webhooks.
  • Shell hooks: a hooks: block in config.yaml that points at any executable script. They run in the CLI, the gateway, and the Desktop, TUI and dashboard chats, and they can block tool calls or inject context.
  • Plugin hooks: Python callbacks registered with ctx.register_hook() inside a plugin. They run in the CLI and the gateway.
  • Outbound webhooks: a hooks.outbound: list that POSTs signed JSON to HTTP endpoints when events fire. Nothing runs on your machine.

Gateway hooks for chat-side alerts

A gateway hook is a folder with two files. HOOK.yaml lists the events it listens to, such as gateway:startup, session:start, session:reset, agent:start, agent:step, agent:end or the wildcard command:*. handler.py defines a function named handle that receives the event type and a context dictionary. It can be async or a regular function. Errors are caught and logged, so a broken hook never crashes the agent.

Be aware of the trust model. Putting the files in the folder is the opt-in. There is no enable list and no sandbox, and the handler runs inside the gateway process with the same access the gateway has. Read any hook's handler.py before you install it, just as you would a plugin. Gateway hooks load once, when the gateway starts, so restart the gateway after adding one.

Shell hooks for guardrails and context

Shell hooks are where most people should start, because they need no Python and work almost everywhere. Each entry names a plugin-hook event, an optional matcher regex (used only for pre_tool_call and post_tool_call), a command, and an optional timeout (default 60 seconds, capped at 300). Hermes sends a JSON payload to the script on stdin and reads JSON back from stdout.

Three uses come up most often. A post_tool_call hook matching write_file|patch can auto-format files the agent just wrote. A pre_tool_call hook matching terminal can block a command by printing {"decision": "block", "reason": "..."}, or simply by exiting with code 2. A pre_llm_call hook can return {"context": "..."} to add something like git status output to the next turn.

The first time Hermes sees a given event and command pair, it asks before running it, and stores your answer in ~/.hermes/shell-hooks-allowlist.json. Only a foreground hermes chat ever shows that prompt. The gateway, cron and other headless runs skip any hook that hasn't been approved and log a warning, so approve new hooks interactively first, or use --accept-hooks, HERMES_ACCEPT_HOOKS=1 or hooks_auto_accept: true if you understand the trade-off.

Two details matter for safety. First, consent is tied to the command string, not the script's contents, so later edits to the script are trusted silently. hermes hooks doctor flags scripts that changed after approval. Second, shell hooks fail open by default: a crash or timeout logs a warning and the tool call goes ahead. For a security check such as a secret scanner, set fail_closed: true on the pre_tool_call entry so a broken check blocks the call instead.

Testing and maintaining hooks

  • hermes hooks list shows configured shell hooks and outbound targets, with matcher, timeout and consent status.
  • hermes hooks test <event> fires matching hooks with a synthetic payload and prints the parsed response. Use --for-tool terminal to test a tool matcher.
  • hermes hooks revoke <command> removes a consent entry. A running gateway stops running that hook within about 30 seconds.
  • hermes hooks doctor checks the executable bit, consent, changed scripts, JSON output and rough run time.
  • The dashboard's System page can also list, create and remove shell hooks, and its create form includes an explicit consent checkbox.

Outbound webhooks to tell other systems

If all you need is for another system to find out that something happened, such as a CI server or a monitoring board, use hooks.outbound: instead of writing a script. Each entry has a url, a list of events (for example on_session_end or subagent_stop), an optional matcher for tool events, and ideally secret_env, which names an environment variable holding the signing secret. Entries without a secret are sent unsigned and shown as UNSIGNED by hermes hooks list. Payloads include tool inputs, so only send them to endpoints you trust, preferably over https://.

Primary sources

Spotted something wrong or out of date? Tell us on X at @HermesWatcher (opens in a new tab) and we will fix it.

New Hermes Agent updates every day on X. Follow @HermesWatcher

Liked this guide?

Every Hermes Agent change that matters, explained in five minutes.

We'll only use your email to send the newsletter. Unsubscribe anytime. Privacy