Permissions, approvals and audit for Nous Research Hermes Agent
Hermes Agent hooks are Python callbacks, pre_tool_call and post_tool_call, that run synchronously around every tool the agent calls: terminal, file, web, browser, vision, cron and custom skills. The acp-hermes plugin registers both. Each call is then allowed, denied or sent to approval, and logged.
Hermes Agent has a built-in Python plugin system. Its hooks cover every tool the agent runs. Codex CLI’s hooks, by comparison, see only Bash. Once the plugin is enabled, no feature flag is needed. That makes Hermes one of the easier agents to put permissions on.
Install
pip install acp-hermes
hermes plugins enable acp
acp-hermes login
Three commands. No curl-pipe-bash and no MCP connector. The first installs the acp-hermes PyPI package. The second registers it with Hermes. The third opens your browser, trades a one-time token for a workspace API key, and writes it to ~/.acp/credentials.
Two things during step 2 are easy to misread:
- Hermes asks whether the plugin may replace built-in tools (
shell_exec,write_file, and so on). Answer no. ACP registerspre_tool_callandpost_tool_callfor policy, plus two read-only metering hooks (post_api_request,post_llm_call) for the local cost ledger. It never overrides a tool and does not registertransform_tool_result. Declining is the right answer, not a half-install. - Enabling prints
Takes effect on next session. You must restarthermes. If you keep working in the same session, you get the most common false alarm: plugin set up, credentials valid, zero logged calls, no errors.
pip install alone registers nothing.
Your first logged call shows up at cloud.agenticcontrolplane.com/logs within seconds of restarting hermes.
Every Hermes tool call lands in one activity log with the cost X-ray next to it, free up to 5 agents. You change policy in one place and read one record.
Want it fully on-device? Not for Hermes yet. The coding-agent installer’s --local flag (no account, decisions on your machine) covers Claude Code, Cursor, and Codex’s shell calls. The acp-hermes plugin needs a workspace token, so if that installer finds Hermes in --local mode it skips it and tells you to re-run without the flag. What does run with no account is local cost metering (below). acp-hermes report reads a SQLite ledger of every model call on your machine. The ledger stays on that one machine, so there is no workspace policy and no shared view.
Why pip and not curl-pipe-bash
The Claude Code, Codex and Cursor installer is curl -sf https://agenticcontrolplane.com/install.sh | bash because those agents run shell hooks. They start govern.mjs, pipe JSON in, and read JSON out. The installer only has to drop one file in place.
Hermes hooks are Python functions that Hermes calls inside its own process. register(ctx) hands them over directly. There is no subprocess and no stdin/stdout. So the integration has to be a Python module Hermes can load, which means pip. Wrapping pip in a curl |
bash script would mean bash guessing which Python to use, two ways to uninstall, and confusing failures when one half breaks. |
So each runtime gets its own install path. The backend, dashboard and policies are the same.
How it works
ACP registers its Hermes hooks through the hermes_agent.plugins.acp entry point:
| Hook | What it does |
|---|---|
pre_tool_call |
POSTs to /govern/tool-use. The server answers allow, deny or ask. deny blocks with a system message. ask hands the call to Hermes’s own once / session / always / deny prompt, in the session; approving there lets it run. allow passes through. |
post_tool_call |
POSTs to /govern/tool-output for observation. Audit, redaction logging and DLP scanning apply on the server. |
Both hooks send X-GS-Client: hermes-plugin/<version>, so the dashboard and audit log can tell Hermes traffic from Claude Code, Codex and Cursor.
The plugin has no dependencies. It uses Python’s standard urllib for HTTP, with no requests, httpx or aiohttp.
What gets installed and where
| Path | Purpose |
|---|---|
<venv>/site-packages/acp_hermes/ |
Plugin code: __init__.py, cli.py, plugin.yaml |
<venv>/bin/acp-hermes |
CLI entry point (login, status, logout) |
~/.acp/credentials |
Bearer token from browser login (chmod 600) |
~/.hermes/config.yaml (Hermes-owned) |
plugins.enabled.acp: true after hermes plugins enable acp |
Nothing is written at system level. To remove it, run hermes plugins disable acp, then pip uninstall acp-hermes.
Coverage
(Cross-agent version: /coverage.)
| Harness | What pre_tool_call covers |
|---|---|
| Hermes | Everything. terminal, file, web, browser, vision, cron, skills, MCP |
| Claude Code | All native tools + MCP |
| Codex CLI | Bash only (the rest needs an MCP connector) |
| Cursor | Most native tools; some built-ins (such as Web Search) don’t emit |
That is why Hermes doesn’t need an MCP connector the way Codex does. The hook already sees the full tool list.
Limitations
post_tool_call only observes
Hermes ignores the return value of post_tool_call, by design. The hook can log output and forward it to the gateway. It can’t block or redact a result.
Hermes has a separate hook for that: transform_tool_result. It runs after post_tool_call and before the result goes into the conversation, and the first string a plugin returns replaces what the model sees. acp-hermes doesn’t register it today, so ACP’s redaction is not applied back to the agent’s context. If your policy needs to catch a secret in terminal output before the model reads it, that is a gap in this plugin, not in Hermes. Payload fields and a worked example are in the Hermes Agent hooks reference.
Two things still help:
- Stopping a call before it runs.
pre_tool_callis fully supported. A rule can deny or ask on a command before it executes. By default, ACP’s safety floor always deniesrm -rfof home or root, and it asks before a force-push orcurl | sh. The floor reads shell command text, and we haven’t confirmed that it reads Hermes’sterminaltool. Uploads and credential-file reads are caught only if you write a rule for them. The secret-in-curl guard is off until a preset turns it on. - The server-side audit still runs. Output is still sent to
/govern/tool-output. You can review redactions in the dashboard, but they are not applied to the agent’s context.
Approvals
When the server answers ask, the plugin passes it to Hermes’s own approval prompt. You choose once, for the session, always, or deny, right in Hermes. This has worked since acp-hermes 0.1.1. The plugin always reports the session as interactive, so Hermes has no separate unattended path. Whether the gateway also records a hold on the console for these asks is not something we’ve checked.
Keep the plugin current with pip install -U acp-hermes.
Hooks are synchronous
Hermes calls hooks synchronously, so the tool call waits for the hook. The plugin uses a 4-second HTTP timeout on /govern/tool-use to cap the wait. After that, the plugin fails open: the tool call goes ahead and [ACP] gateway unreachable is written to stderr.
What you’ll see in the dashboard
Once the plugin is live, cloud.agenticcontrolplane.com/agents shows a hermes-plugin row. Tool calls are tagged with the Hermes task_id (Hermes’s session ID), which the gateway maps to ACP session context.
The activity log includes:
tool_name, such asterminal,web_search,read_file, plus custom skill tool namestool_input, the dict the model passed to the tooltool_output, the result, cut at 200 KB (the backend scan limit)duration_ms, Hermes’s measured run time- The usual ACP fields: workspace, user, policy decisions, delegation chain
Since acp-hermes 0.2.0, the plugin also meters model cost on your machine. Every model call goes into a SQLite ledger (tokens, cache reads, per-model spend), with no account needed. See it any time:
acp-hermes report
Local metering keeps everything on your machine. For the full cloud cost X-ray (loop vs. leaf cost, prompt-cache economics, per-run sessions in the console), send model calls through the proxy below.
Naming note: the PyPI package and the CLI are both acp-hermes. The package was renamed from hermes-acp in 0.2.5, and the old name still installs but is no longer updated. Hermes itself ships a hermes-acp binary for its editor integration (there, ACP means Agent Client Protocol, a name collision). python -m acp_hermes.cli always works.
Running Hermes over Agent Client Protocol (the editor protocol, not us)
If you searched hermes acp for the editor integration: Hermes runs as an Agent Client Protocol server over stdio, per its Agent Client Protocol docs. Start it with hermes acp (or hermes-acp, or python -m acp_adapter) after adding the agent-client-protocol dependency (the docs give a one-line pm.sync_venv(['acp']) command). In Zed, add a custom entry under agent_servers with command hermes and args ["acp"]. VS Code’s ACP Client extension (an Agent Client Protocol client) lists Hermes Agent in its built-in agent list.
In that mode Hermes uses a smaller hermes-acp toolset meant for editors, and it can send dangerous terminal commands back to the editor as approval prompts. On a timeout or error, that bridge denies. The docs also note that a host may answer those prompts itself without showing you.
We have not tested acp-hermes hooks under hermes acp. Hermes’s hooks docs say plugin hooks fire in CLI and gateway sessions. A few session hooks also name ACP sessions, but the docs don’t say whether pre_tool_call fires there. Until the coverage matrix lists it, don’t count on an editor-driven Hermes session being checked or recorded by ACP.
Get the cost X-ray: send Hermes’s model calls through the proxy
The plugin is one of ACP’s two capture points. It checks and logs every tool call. The other, the model proxy, meters every model call. Cost, prompt-cache hit rate and the loop-vs-leaf X-ray come from the proxy.
This matters for Hermes because it often runs unattended against a paid API. Without it, a cost regression shows up on next month’s bill. The proxy shows spend per call, per run and per agent as it happens.
There are two ways to turn it on. The first is per launch, with the same launcher pattern as claude-acp and codex-acp (the installer puts it in ~/.acp/bin):
hermes-acp
hermes-acp reads your workspace key from ~/.acp/credentials, exports it as ACP_BEARER_TOKEN, and points an Anthropic-native backend at the proxy for that launch only (ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN). If the gateway is unreachable it starts plain hermes with a one-line note. Plain hermes stays untouched.
~/.acp/bin/hermes-acp is ACP’s cost launcher. It has the same name as Hermes’s own editor binary, so if ~/.acp/bin comes earlier on your PATH, it shadows that binary. The plugin’s compatibility script uses the name too, and install order decides which one wins.
The second way is persistent, and works with any backend including OpenAI-compatible ones (0.2.2+):
acp-hermes proxy-setup --verify
It reads Hermes’s own config, registers ACP as a named provider pointed at the proxy, and keeps your current model. --verify sends one metered completion to prove the wiring works. --undo reverses everything. If you’d rather wire it by hand:
# OpenAI-compatible model backends
# base_url = https://api.agenticcontrolplane.com/v1
# Anthropic-native model backends
export ANTHROPIC_BASE_URL="https://api.agenticcontrolplane.com/anthropic/v1"
export ANTHROPIC_AUTH_TOKEN="gsk_yourslug_..." # an ACP key, not a provider key
The proxy routes gpt-*, claude-* and gemini-* by model id and forwards to the real provider unchanged. You get the same responses, now metered. It also checks the tool-use blocks the model emits against your policy, which adds a second allow/deny check on top of the plugin’s hooks.
Full walkthrough, including the per-agent tagging header and what each metric means: Turn on Cost X-Ray.
Run both for complete coverage. The plugin checks tools where they run, including native terminal, file and browser actions that never touch the model API. The proxy meters cost and checks the tools the model asks for.
Setting up policy
The same three-part model applies (tool, agent and user policies). For Hermes:
- Tool policies. Restrict
terminalsubcommands the way you would for Claude Code’sBash. A denylist plus a scoped allowlist works the same. - Agent policies. Hermes can run as a CLI, a messaging gateway (Discord, Telegram, Slack and others) or an IDE integration. Each sends a distinct
client_idin the gateway request and can carry its own policy. - User policies. Limit Hermes enterprise installs to specific identities. Per-platform attribution lets you write “Slack-platform Hermes can read but not write.”
CLI reference
acp-hermes login # browser-based authentication + workspace provisioning
acp-hermes status # check creds + gateway reachability
acp-hermes logout # remove ~/.acp/credentials
acp-hermes login is safe to re-run. On a machine that already has credentials it prints Credentials already at ~/.acp/credentials. Re-run with --force to reconfigure.
Troubleshooting
Hook isn’t firing at all. Check that the plugin is enabled with hermes plugins list. If acp isn’t in the enabled list, run hermes plugins enable acp, then restart hermes.
No audit events appearing. The plugin reads ACP_BEARER_TOKEN from the environment of the process that launched hermes. If you exported it after starting Hermes, restart. Or confirm ~/.acp/credentials exists and isn’t empty (acp-hermes status).
[ACP] gateway unreachable in stderr. A network failure, and the plugin failed open. Tool calls are going ahead with no policy check or audit. Test with curl https://api.agenticcontrolplane.com/govern/health.
Every tool call stops for approval. The server is answering ask for everything. Look at your workspace policies for an over-broad “ask on any high-risk tool” rule. Approve what you need in the Hermes prompt, then tighten the rule.
Tool call blocked but no entry in dashboard. The pre-call check hit a network error and a stale denial cache held over from an earlier session. Run acp-hermes logout && acp-hermes login to refresh.
Frequently asked questions
What are hermes agent hooks?
Python plugin callbacks that Hermes Agent runs synchronously around every tool call: pre_tool_call before it runs and post_tool_call after. They cover terminal, file, web, browser, vision, cron and custom skills. The plugin system is built in, but a plugin’s hooks run only once that plugin is enabled with hermes plugins enable <name>.
Can hermes agent hooks block a tool call?
pre_tool_call can. ACP’s plugin uses it to deny a call with a system message, or to hand the call to Hermes’s own approval prompt when policy says ask. post_tool_call only observes; Hermes ignores its return value by design. Changing or redacting a result is a separate hook, transform_tool_result, covered in the Hermes Agent hooks reference at /blog/hermes-agent-hooks-reference.
Why are my hermes agent hooks not firing?
Usually because Hermes is still in the session where you ran hermes plugins enable acp, which prints Takes effect on next session. Restart hermes. Order matters too: pip install acp-hermes on its own registers nothing until the plugin is enabled.
Related integrations
- Claude Code: a different runtime (shell hook), same backend contract
- Codex CLI: partial hook coverage, needs an MCP supplement
- Cursor: IDE-based, hook-based permissions
- Agent-to-Agent delegation: how delegation chains carry identity through Hermes’s session lineage
Protected in 3 steps
Out of the box, ACP records every tool call your agents make. A short safety floor applies in every mode: catastrophic commands (rm -rf on your home or system folders, disk wipes) are always blocked, and force-push, destructive SQL, piping a download into a shell, and deleting outside your project are held for your approval. Your own rules (ask first / never) start applying when you turn on enforce. No rule can loosen the floor; a workspace owner or admin can switch it off on the Policies page (you were asked at install). With --local the always-blocked list is narrower: rm -rf on your home or root, disk wipes, fork bombs.
-
1
Install. One command, then approve this machine in the browser.
curl -fsSL https://agenticcontrolplane.com/install.sh | bash -
2
Turn on your rules. Run
/acp-enforcein your agent. The starter rules start applying:rmand keychain reads are held for your approval (you approve on the console Approvals page or from the approval email); everything else keeps running and is recorded. Add your own with/acp-ask Bash.curl,/acp-deny Bash.git.push,/acp-allow Bash.npm, or on the Policies page. With--localthere is no mode switch: the rules in~/.acp/policy.jsonapply from the first call, and the starter file asks beforerm,curl,git push,chmodandchown. -
3
See it work. Ask your agent to do something risky, for example
git push --forceon a scratch repo. ACP holds the call for your approval; the call and your answer land in the audit log (/acp-whyfor the last hold, the console Activity page, or~/.acp/audit.jsonlwith--local). In Hermes, a held call shows as a block in the agent; approve it on the console Approvals page or from the approval email, then ask the agent to retry.
Copy-paste rules for the things people are most afraid of (force-push, prod database, rm -rf, a secret in a curl, overnight runs, a cost cap): Rules that stop the scary things.