# Hermes Agent Audit Log, Hooks & Governance — Install Guide

Install ACP for Nous Research Hermes Agent via pip. Native pre/post tool-call hooks cover every Hermes tool — terminal, file, web, browser, vision, and custom skills — with full audit log and policy enforcement.

# Govern Nous Research Hermes Agent with Agentic Control Plane

[Hermes Agent](https://github.com/NousResearch/hermes-agent) ships a first-class Python plugin system with synchronous `pre_tool_call` and `post_tool_call` hooks that cover **every** tool the agent runs — terminal, file, web, browser, vision, cron, custom skills. Unlike Claude Code or Codex CLI (where hook coverage is partial), Hermes hooks are universal and on by default. That makes it the cleanest of the major coding agents to govern.

## TL;DR

```bash
pip install acp-hermes
hermes plugins enable acp
acp-hermes login
```

Three commands. No curl-pipe-bash, no feature flags to flip, no MCP connector required for tool coverage. The first line installs the [acp-hermes PyPI package](https://pypi.org/project/acp-hermes/). The second registers it with Hermes. The third opens your browser, exchanges a one-time auth token for a workspace API key, and writes it to `~/.acp/credentials`.

Two things happen during step 2 that are easy to misread:

- **Hermes asks whether the plugin may replace built-in tools** (`shell_exec`, `write_file`, …). **Answer no.** ACP registers only `pre_tool_call` and `post_tool_call` — it never needs tool override, and governance works fully without it. Declining is the correct answer, not a half-install.
- **Enabling prints `Takes effect on next session`.** You must **restart `hermes`**. Enabling and then continuing in the same session produces the most common false alarm we see: plugin provisioned, credentials valid, zero governed calls, no errors.

Order matters, too — `pip install` on its own registers nothing.

You'll see your first governed tool call in [cloud.agenticcontrolplane.com/logs](https://cloud.agenticcontrolplane.com/logs) within seconds of restarting `hermes`.

That login is the default, and the mode to want: every Hermes tool call — terminal, file, web, browser, custom skills — lands in one activity log with the cost X-ray alongside it, free up to 5 agents. Policy you can change in one place, and a record you can actually read.

**Prefer fully on-device?** Not for Hermes yet. The coding-agent installer's `--local` flag (no account, decisions on-device) covers Claude Code, Cursor, and Codex's shell calls. The `acp-hermes` plugin needs a workspace token, so if that installer detects Hermes in `--local` mode it skips it and says to re-run without the flag. What does run with zero account is local cost metering (below): `acp-hermes report` reads a SQLite ledger of every model call from your machine. The trade is that the ledger stays on that one machine — no workspace policy, no shared activity view, nothing to compare across agents.

## Why pip instead of curl-pipe-bash?

The Claude Code / Codex / Cursor installer is `curl -sf https://agenticcontrolplane.com/install.sh | bash` because those harnesses use **out-of-process shell hooks** — bash spawns `govern.mjs`, pipes JSON in on stdin, reads JSON out on stdout. The installer just drops one file in the right place.

Hermes hooks are **in-process Python callbacks**: `register(ctx)` registers functions that Hermes calls directly inside its own runtime. There's no subprocess boundary, no stdin/stdout protocol — it's a function call. That means the integration must be a Python module loadable by Hermes's plugin system, which means pip. Forcing pip inside a curl|bash wrapper would be a worse UX — bash shelling out to whichever Python it finds first, two uninstall paths, and confusing failure modes when one half fails.

So: separate install paths, native to each runtime. Same backend, same dashboard, same policies.

## How it works

ACP registers two Hermes hooks via the `hermes_agent.plugins.acp` entry point:

| Hook              | What it does                                                                                                          |
|-------------------|-----------------------------------------------------------------------------------------------------------------------|
| `pre_tool_call`   | POSTs to `/govern/tool-use`. Server returns `allow` / `deny` / `ask`. `deny` blocks with a system message; `ask` escalates to Hermes's native approval prompt; `allow` passes through. |
| `post_tool_call`  | POSTs to `/govern/tool-output` for observation. Server-side audit, redaction logging, and DLP scanning all apply.    |

Both hooks send `X-GS-Client: hermes-plugin/<version>` so the dashboard, policy router, and audit log can distinguish Hermes traffic from Claude Code / Codex / Cursor / etc.

The plugin is **zero-dependency** — it uses Python's stdlib `urllib` for HTTP, no `requests` / `httpx` / `aiohttp` pulled in. This keeps the supply-chain surface tight and matches the discipline we use for the rest of the ACP packages.

## 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 OAuth (`chmod 600`)      |
| `~/.hermes/config.yaml` (Hermes-owned)    | `plugins.enabled.acp: true` after `hermes plugins enable acp` |

No system-level file writes. Removing it is `hermes plugins disable acp` followed by removing the `acp-hermes` package with pip.

## Coverage — Hermes is the cleanest of the major coding agents

*(Living cross-harness version: [/coverage](/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 (rest needs MCP connector supplement)                            |
| Cursor      | Most native tools; some built-ins (e.g. Web Search) don't emit             |

This is why Hermes doesn't need an MCP connector supplement the way Codex does — the hook surface already covers the full tool catalog.

## Limitations — read before relying on Hermes hooks alone

### `post_tool_call` is observational only

Hermes's `post_tool_call` hook return value is ignored — by design. The hook can log and forward output to the gateway, but it cannot block or redact a tool's result the way Claude Code's `PostToolUse` can. If your governance model depends on **post-hoc redaction** (catching a secret in `terminal` output before it reaches the model), this is a gap.

ACP works around this two ways:

- **Pre-call denial covers the destructive path.** Most policy violations you actually care about — `rm -rf`, exfiltration, credential reads — are catchable at `pre_tool_call`, which Hermes fully supports.
- **Server-side audit still fires.** Output is still POSTed to `/govern/tool-output`. The redaction is visible in the dashboard for post-hoc review and DLP analytics, just not applied back to the agent's context.

### Native inline approvals (acp-hermes ≥ 0.1.1)

When the server returns `decision: "ask"`, the plugin escalates to **Hermes's own human-approval gate** — the same inline `[o]nce / [s]ession / [a]lways / [d]eny` prompt Hermes uses for dangerous shell commands. You answer in the terminal, mid-run, without leaving the agent. An `[a]lways` answer is scoped to the specific tool ACP flagged (the plugin passes `rule_key: acp:<tool>`), so a standing approval never silently widens.

Versions ≤ 0.1.0 hard-blocked `ask` decisions with a "approve in the dashboard and retry" detour — `pip install -U acp-hermes` to get the native flow.

### Hooks are synchronous

Hermes calls hooks synchronously, blocking the tool call until the hook returns. We use a 4-second HTTP timeout to `/govern/tool-use` to bound the worst case. Beyond the timeout, the plugin **fails open** — the tool call proceeds and a `[ACP] gateway unreachable` warning is written to stderr.

## What you'll see in the dashboard

Once Hermes is governed, [cloud.agenticcontrolplane.com/agents](https://cloud.agenticcontrolplane.com/agents) shows a `hermes-plugin` row with activity. Tool calls are tagged with the Hermes `task_id` (Hermes's session identifier — the gateway maps this to ACP session context).

The activity log includes:

- `tool_name` — e.g. `terminal`, `web_search`, `read_file`, plus any custom skill tool names
- `tool_input` — the dict the model passed to the tool
- `tool_output` — the result, truncated to 200 KB (matches the backend scan ceiling)
- `duration_ms` — Hermes's measured execution time
- Standard ACP attribution — workspace, user, policy decisions, delegation chain

As of **acp-hermes 0.2.0**, the plugin also meters **model cost locally**: every model call lands in a SQLite ledger on your machine (tokens, cache reads, per-model spend), with zero account required. See it any time:

```bash
acp-hermes report
```

Local metering is the self-contained mode — nothing leaves your machine. For the full cloud cost X-ray (loop-vs-leaf, prompt-cache economics, per-run sessions in the console), route model calls through the proxy below.

**Naming note:** the PyPI package and the CLI are both **`acp-hermes`** (package renamed from `hermes-acp` in 0.2.5; the old name still installs but is no longer updated). Hermes itself ships a `hermes-acp` binary for its editor integration (ACP there = *Agent Client Protocol* — a name collision), so we use the unambiguous name. `python -m acp_hermes.cli` also always works.

## Get the cost X-ray — route Hermes's model calls through the proxy

The plugin above is one of ACP's two capture points. It governs and audits every **tool** call. The other capture point — the **model proxy** — meters every **model** call, and that's where cost, prompt-cache hit rate, and the loop-vs-leaf cost X-ray come from.

For Hermes especially, this is the high-value half: it runs unattended, often against a paid API, so a cost regression surfaces on next month's bill instead of the moment it happens. The proxy makes spend visible per call, per run, and per agent in real time.

Two ways to turn it on. Per launch, with the same launcher contract as `claude-acp` and `codex-acp` (the installer puts it in `~/.acp/bin`):

```bash
hermes-acp
```

`hermes-acp` reads your workspace key from `~/.acp/credentials`, exports it as `ACP_BEARER_TOKEN`, points an Anthropic-native backend at the proxy for that launch only (`ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN`), and starts plain `hermes` with a one-line note if the gateway is unreachable. Plain `hermes` stays untouched.

Persistently, for any backend including OpenAI-compatible ones (0.2.2+):

```bash
acp-hermes proxy-setup --verify
```

It reads Hermes's own config, registers ACP as a named provider pointed at the proxy, keeps your current model, and `--verify` sends one governed completion to prove the wiring. `--undo` reverses everything. Manual alternative, if you prefer to wire it yourself:

```bash
# 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 is multi-provider (routes `gpt-*`, `claude-*`, `gemini-*` by model id) and forwards to the real provider unchanged — same responses, now metered. It also governs the tool-use blocks the model emits, so the proxy adds a second layer of tool governance 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](/cost-tracking).

Run both together for complete coverage: the **plugin** governs tools at the point they execute (including native terminal/file/browser actions that never hit the model API), and the **proxy** meters cost and governs the tools the model asks for.

## Setting up policy

The same three-axis model applies (Tool / Agent / User policies). Specifically for Hermes:

- **Tool policies** — restrict `terminal` subcommands the same way you would for Claude Code's `Bash`. Pattern-based denylist + scoped allowlist works identically.
- **Agent policies** — Hermes can run as a CLI, a messaging gateway (Discord/Telegram/Slack/etc.), or an IDE integration. Each surface sends a distinct `client_id` in the gateway request and can carry a different policy.
- **User policies** — gate Hermes enterprise installs to specific identities. Multi-platform attribution lets you write "Slack-platform Hermes can read but not write."

## CLI reference

```bash
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 idempotent — running it on a machine that already has credentials prints `Credentials already at ~/.acp/credentials. Re-run with --force to reconfigure.`

## Troubleshooting

**Hook isn't firing at all.** Confirm the plugin is enabled: `hermes plugins list`. If `acp` isn't in the enabled list, run `hermes plugins enable acp`. After enabling, 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. Alternative: confirm `~/.acp/credentials` exists and is non-empty (`acp-hermes status`).

**`[ACP] gateway unreachable` in stderr.** Network failure — plugin failed open. Tool calls are proceeding without governance. Check `curl https://api.agenticcontrolplane.com/govern/health`.

**Every tool call prompting for approval.** Server is returning `decision: "ask"` for everything. Check your workspace's policies — likely an over-broad "ask on any high-risk tool" rule. Answer `[s]ession` to quiet the current run, then tighten the rule (or keep `[a]lways`-ing the tools you trust — each answer is tool-scoped).

**Tool call blocked but no entry in dashboard.** Pre-call check returned a network error AND a stale denial cache held over from a prior session. Run `acp-hermes logout && acp-hermes login` to refresh.

## Related integrations

- [Claude Code](/integrations/claude-code) — different runtime (shell-hook), same backend contract
- [Codex CLI](/integrations/codex) — partial hook coverage, needs MCP supplement
- [Cursor](/integrations/cursor) — IDE-based, hook-based governance
- [Agent-to-Agent governance](/agent-to-agent) — how delegation chains carry identity through Hermes's session lineage

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "Install Agentic Control Plane in Hermes Agent",
  "description": "Install the acp-hermes plugin via pip: allow / ask / deny policy on every Hermes tool call, one shared activity log, and the cost X-ray.",
  "totalTime": "PT1M",
  "step": [
    {"@type": "HowToStep", "name": "Install plugin", "text": "pip install acp-hermes"},
    {"@type": "HowToStep", "name": "Enable plugin in Hermes", "text": "hermes plugins enable acp — answer no when Hermes asks whether the plugin may replace built-in tools; ACP only needs its two hooks."},
    {"@type": "HowToStep", "name": "Authenticate", "text": "acp-hermes login — your browser opens to provision a workspace, and the API key is saved to ~/.acp/credentials. Free up to 5 agents, and you get team policy plus the cost X-ray."},
    {"@type": "HowToStep", "name": "Restart Hermes", "text": "Restart any running Hermes sessions to load the plugin"},
    {"@type": "HowToStep", "name": "(Optional) Local cost metering, no account", "text": "acp-hermes 0.2.0+ also records every model call (tokens, cache reads, per-model spend) to a SQLite ledger on your machine with no credentials; read it with acp-hermes report. There is no --local mode for Hermes: policy decisions and the shared activity log need the workspace, and the coding-agent installer skips Hermes under --local."}
  ]
}
</script>

