Follow-up to #42 (the Hermes adapter I QA'd). The merged adapter wires the gortex MCP server + skills into ~/.hermes, but not hooks — whereas the Claude Code adapter additionally installs PreToolUse hooks (--hook-mode deny|enrich|consult-unlock|nudge, per gortex install --help) that redirect Grep/Glob/Read of indexed source to graph tools. Hermes ships an equivalent primitive, so this is a clean parity gap.
Hermes supports exactly the right primitive
Verified against Hermes Agent v0.15.1, official docs website/docs/user-guide/features/hooks.md:
pre_tool_call shell hooks, declared in the hooks: block of ~/.hermes/config.yaml, can block a tool call. (hooks: {} is also present in every per-profile config.yaml — same shape as mcp_servers.)
- JSON wire protocol: Hermes pipes
{"hook_event_name","tool_name","tool_input","session_id","cwd","extra"} to the hook script's stdin and reads a decision from stdout.
- It already accepts the Claude-Code block shape verbatim — the docs list both as equivalent:
So gortex's existing Claude Code hook output needs little-to-no translation.
- Matcher: optional regex on
tool_name, used for pre_tool_call / post_tool_call.
pre_llm_call is the documented UserPromptSubmit equivalent (context injection) — covers an enrich posture. The docs say so explicitly: "Claude Code's UserPromptSubmit event is intentionally not a separate Hermes event — pre_llm_call fires at the same place and already supports context injection."
- The
hermes hooks CLI (list / test / doctor) is built-in for verification.
Proposal
Extend the Hermes adapter so gortex install --agents=hermes [--hooks --hook-mode ...] upserts a pre_tool_call shell hook (and, for enrich, a pre_llm_call hook) into the hooks: block, mirroring the Claude Code treatment.
Tool-name mapping (Hermes's tool surface differs from Claude Code's):
- File reads →
read_file (matcher read_file): block + redirect to get_symbol_source / graph read_file when the path is indexed source.
- grep / find / cat → inside the
terminal tool (matcher terminal): inspect tool_input.command for grep|rg|find|cat against indexed paths and redirect to search_symbols / find_usages. Hermes has no separate Grep/Glob tool — that shell-tool indirection is the one structural difference from the Claude Code adapter.
The hook script asks the running daemon whether the target is indexed (the same decision the Claude Code hook already makes) and returns the block-with-redirect message.
Why it's low-cost
- The stdout protocol already speaks the Claude-Code
{"decision":"block","reason":...} shape → reuse the existing hook's output path.
- The daemon is already running; the indexed-or-not decision is identical to Claude Code.
- The
hooks: write rides the same atomic-write + comment-preserving MergeYAML the adapter just added for mcp_servers.
Caveats / open questions (flagging honestly)
- Consent: shell hooks require first-use consent. Non-TTY contexts (gateway, cron) need
--accept-hooks, HERMES_ACCEPT_HOOKS=1, or hooks_auto_accept: true, else a newly-added hook silently stays unregistered (and logs a warning). The installer should at least surface this in its output / docs.
- Security: the
hooks: block runs scripts with full user credentials. Writing into it is privileged — keep it opt-in (--hooks) and log exactly what was written, same posture as the Claude Code adapter.
- Per-profile vs global:
hooks: {} is present in both the global and every profile config here (same shape as mcp_servers), but the docs only document the global config.yaml. Worth confirming whether per-profile shell hooks are actually honored before deciding per-profile vs global-only. If global-only, this is simpler than the mcp_servers per-profile dance.
- Script location: docs convention is
~/.hermes/agent-hooks/; the installer should drop the script there and reference it by path.
Acceptance
gortex install --agents=hermes --hooks writes a valid pre_tool_call (and optional pre_llm_call) entry; --no-hooks skips; idempotent re-run is a no-op.
hermes hooks list shows the entry; hermes hooks test pre_tool_call --for-tool read_file returns a block→redirect for an indexed path and {} otherwise.
init doctor reports it; the uninstall note covers it.
Environment: Hermes Agent v0.15.1 · gortex v0.35.3 · docs website/docs/user-guide/features/hooks.md.
Follow-up to #42 (the Hermes adapter I QA'd). The merged adapter wires the
gortexMCP server + skills into~/.hermes, but not hooks — whereas the Claude Code adapter additionally installs PreToolUse hooks (--hook-mode deny|enrich|consult-unlock|nudge, pergortex install --help) that redirectGrep/Glob/Readof indexed source to graph tools. Hermes ships an equivalent primitive, so this is a clean parity gap.Hermes supports exactly the right primitive
Verified against Hermes Agent v0.15.1, official docs
website/docs/user-guide/features/hooks.md:pre_tool_callshell hooks, declared in thehooks:block of~/.hermes/config.yaml, can block a tool call. (hooks: {}is also present in every per-profileconfig.yaml— same shape asmcp_servers.){"hook_event_name","tool_name","tool_input","session_id","cwd","extra"}to the hook script's stdin and reads a decision from stdout.{"decision": "block", "reason": "..."} // Claude-Code style {"action": "block", "message": "..."} // Hermes-canonicaltool_name, used forpre_tool_call/post_tool_call.pre_llm_callis the documentedUserPromptSubmitequivalent (context injection) — covers anenrichposture. The docs say so explicitly: "Claude Code's UserPromptSubmit event is intentionally not a separate Hermes event — pre_llm_call fires at the same place and already supports context injection."hermes hooksCLI (list/test/doctor) is built-in for verification.Proposal
Extend the Hermes adapter so
gortex install --agents=hermes [--hooks --hook-mode ...]upserts apre_tool_callshell hook (and, forenrich, apre_llm_callhook) into thehooks:block, mirroring the Claude Code treatment.Tool-name mapping (Hermes's tool surface differs from Claude Code's):
read_file(matcherread_file): block + redirect toget_symbol_source/ graphread_filewhen the path is indexed source.terminaltool (matcherterminal): inspecttool_input.commandforgrep|rg|find|catagainst indexed paths and redirect tosearch_symbols/find_usages. Hermes has no separateGrep/Globtool — that shell-tool indirection is the one structural difference from the Claude Code adapter.The hook script asks the running daemon whether the target is indexed (the same decision the Claude Code hook already makes) and returns the block-with-redirect message.
Why it's low-cost
{"decision":"block","reason":...}shape → reuse the existing hook's output path.hooks:write rides the same atomic-write + comment-preservingMergeYAMLthe adapter just added formcp_servers.Caveats / open questions (flagging honestly)
--accept-hooks,HERMES_ACCEPT_HOOKS=1, orhooks_auto_accept: true, else a newly-added hook silently stays unregistered (and logs a warning). The installer should at least surface this in its output / docs.hooks:block runs scripts with full user credentials. Writing into it is privileged — keep it opt-in (--hooks) and log exactly what was written, same posture as the Claude Code adapter.hooks: {}is present in both the global and every profile config here (same shape asmcp_servers), but the docs only document the globalconfig.yaml. Worth confirming whether per-profile shell hooks are actually honored before deciding per-profile vs global-only. If global-only, this is simpler than themcp_serversper-profile dance.~/.hermes/agent-hooks/; the installer should drop the script there and reference it by path.Acceptance
gortex install --agents=hermes --hookswrites a validpre_tool_call(and optionalpre_llm_call) entry;--no-hooksskips; idempotent re-run is a no-op.hermes hooks listshows the entry;hermes hooks test pre_tool_call --for-tool read_filereturns a block→redirect for an indexed path and{}otherwise.init doctorreports it; the uninstall note covers it.Environment: Hermes Agent v0.15.1 · gortex v0.35.3 · docs
website/docs/user-guide/features/hooks.md.