Overview
Hermes Agent (Nous Research) has a first-class hook system built for exactly this. Itspre_tool_call hook fires
immediately before every tool executes, built-in tools and plugin tools
alike, and can veto the call. Sigil Open Framework (SOF) registers a shell hook
that forwards each intended tool call to Sigil Sign /v1/authorize and blocks
when the policy returns DENIED.
Of the popular agent runtimes, Hermes offers the most complete pre-execution
surface: a single hook governs terminal, write_file, patch, web_search,
read_file, and any plugin or MCP tool the agent can reach.
@sigilcore/agent-hooks ships a dedicated Hermes export:
createHermesPreToolCallHook. It normalizes Hermes hook payloads, maps common
tool names to Sigil actions, resolves task ids, and returns Hermesβ block shape
consistently.
HTTP note: the adapter promotes a web call to typed http only when its input explicitly contains a valid method. It does not infer GET; otherwise the call remains web_fetch.
Prerequisites
You need a Sigil API key and a signedwarranty.md policy file deployed to Sigil Sign.
- Get an API key: sigilcore.com/tools/keys
- Generate a policy: sigilcore.com/tools/warrant
1. Add the shell hook
Hermes shell hooks are declared in~/.hermes/config.yaml and run as
subprocesses when the matching event fires, in both CLI and gateway sessions.
matcher is a regex over the tool name. Widen or narrow it to match the
actions your policy governs.
2. Add the hook script
~/.hermes/agent-hooks/sigil-pre-tool-call.mjs:
SIGIL_API_KEY in your environment. On first use Hermes prompts once to
approve the (event, command) pair and persists the decision. For non-interactive
gateway or cron runs, pre-approve with HERMES_ACCEPT_HOOKS=1 or
hooks_auto_accept: true in config.yaml.
The adapter resolves task ids in this order: SIGIL_TASK_ID, config.taskId,
session_id, conversation_id, then run_id. ## execution_limits uses that
value to stop runaway tool loops within one task.
Model Budget Brakes
The shell hook above gates tool execution only. It does not see provider token usage by itself. To enforcemax_model_spend_usd_per_task or
max_model_tokens_per_task, the Hermes host or plugin must record provider
usage after model calls with recordModelUsage and call checkModelBudget with
the same task id.
How It Works
Hermes pipes a JSON payload to the hook onstdin and reads JSON back from
stdout. The script maps the Hermes tool name to a Sigil action type, submits the
intent to /v1/authorize, and on a DENIED or PENDING decision returns the
canonical block shape. Hermes then short-circuits the tool and hands the reason
back to the model as the tool error.
{"decision": "block", "reason": "..."} and
{"action": "block", "message": "..."}.
Tool Name Mapping
Plugin Hook Alternative
If you ship a Hermes plugin, you can register the same check in-process instead of as a subprocess. In your pluginβsregister():
Fail Mode
The script usesfailMode: 'closed', so a tool is blocked if Sigil Sign is
unreachable. Switch to failMode: 'open' for local development. Use closed mode
for any environment that touches production, external systems, or on-chain actions.
Configuration
Troubleshooting a hook that never fires
A declared hook that never registers is the first thing to check, because nothing reports an error when it happens. The agent keeps running, no tool call is blocked, and Sigil looks installed while every action executes ungoverned. Two causes account for most of it. Keep thehooks: block in ~/.hermes/config.yaml. That is the active
config file and the one step 1 uses. Nousβs hooks page also refers to
cli-config.yaml in two places, which points at cli-config.yaml.example, a
commented reference file rather than a config Hermes loads. A hooks: block
placed there never registers and never raises an error, so the agent runs
ungoverned while the file looks correct.
Pre-approve the hook on any non-interactive run. Hermes prompts once to
approve each (event, command) pair and remembers the answer. A gateway, cron,
or CI run has no terminal to answer that prompt, and Nous states the consequence
plainly, which is that a newly added hook silently stays unregistered. Set one
of the three escape hatches before the first non-interactive run.
HERMES_ACCEPT_HOOKS=1in the environment--accept-hookson the CLI, for examplehermes --accept-hooks chathooks_auto_accept: truein the config file
HERMES_ACCEPT_HOOKS=1 on the specific service unit
over hooks_auto_accept: true, which blanket-approves every hook the config
can introduce.
Verify enforcement instead of assuming it. After install, run a tool call
your policy denies and confirm Hermes blocks it. A passing deny is the only
evidence that the hook registered. failMode: 'closed' does not cover this
case, because a hook that never runs has no fail mode to apply.
Source
- github.com/Sigil-Core/agent-hooks β TypeScript package, MIT License
- Hermes Agent Event Hooks documentation