Metadata-Version: 2.4
Name: atbash-hermes-plugin
Version: 0.15.1.dev0
Summary: Atbash safety plugin for Hermes Agent
Author: atbash
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/Atbash-Ai/atbash-hermes-plugin
Project-URL: Repository, https://github.com/Atbash-Ai/atbash-hermes-plugin
Keywords: atbash,hermes,hermes-agent,agent-safety,ai-safety,tool-guard,judge,policy
Requires-Python: <3.13,>=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: atbash-sdk<1.0.0,>=0.15.0.dev0
Requires-Dist: httpx<1,>=0.27
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.29
Requires-Dist: opentelemetry-sdk<2,>=1.29
Dynamic: license-file

# Atbash Hermes Plugin

Atbash guardrail plugin for [Hermes Agent](https://hermes-agent.nousresearch.com/)
using the official Python SDK (`atbash-sdk`).

The plugin registers a Hermes `pre_tool_call` hook and asks Atbash for a verdict
before a Hermes tool runs. If Atbash returns a blocking decision, the tool call is
stopped before execution.

## What It Does

- Intercepts Hermes tool calls through `pre_tool_call`.
- Sends the tool name, arguments, command-like payload, session metadata, and
  inferred action class to Atbash.
- Blocks tool execution on `BLOCK`, `DENY`, `REJECT`, `DISALLOW`, or `HOLD`.
- Persists learned Hermes tool classifications across sessions.

## Install

Install the plugin into the same Python environment that runs Hermes. If Hermes runs in a virtual environment (the common case), use that environment's Python directly — the plain `pip` command below only applies if Hermes runs in your default Python environment:

```bash
pip install atbash-hermes-plugin
```

For a virtual environment, install into that environment's Python:

```bash
/path/to/hermes/venv/bin/python -m pip install atbash-hermes-plugin
```

On most machines this is `~/.hermes/hermes-agent/venv/bin/python`. If you installed via `uv`:

```bash
uv pip install --python /path/to/hermes/venv/bin/python atbash-hermes-plugin
```

## Update

To update an existing install, use the same path you used to install:

```bash
/path/to/hermes/venv/bin/python -m pip install --upgrade atbash-hermes-plugin
```

Or with `uv`:

```bash
uv pip install --python /path/to/hermes/venv/bin/python --upgrade atbash-hermes-plugin
```

> **Note:** `hermes plugins update atbash-hermes-plugin` does not work — Hermes's built-in update command only applies to plugins installed from git.

Maintainers: releases use clean staged source, live PyPI monotonic checks, and
Trusted Publishing. See [the release process](docs/release.md).

## Configure Atbash

The plugin needs an Atbash agent key. Configure either `ATBASH_KEY_PATH` or
`ATBASH_AGENT_PRIVKEY`.

Recommended:

```bash
ATBASH_KEY_PATH=$HOME/.config/atbash/guard-client-key
```

Alternative:

```bash
ATBASH_AGENT_PRIVKEY='{"pubkey":"...","privkey":"..."}'
```

## Where To Set Environment Variables

Hermes commonly loads environment variables from `~/.hermes/.env`.

Create or edit that file:

```bash
nano ~/.hermes/.env
```

Add:

```bash
ATBASH_KEY_PATH=$HOME/.config/atbash/guard-client-key
ATBASH_ENFORCE_DECISION=true
ATBASH_DEBUG=false
ATBASH_ORG_NAME=your-org-name
```

Restart Hermes after changing `.env`.

For a one-off terminal session, you can also export variables before starting
Hermes:

```bash
export ATBASH_KEY_PATH="$HOME/.config/atbash/guard-client-key"
export ATBASH_ENFORCE_DECISION=true
export ATBASH_DEBUG=false
hermes
```

## Optional Settings

```bash
# Override the Atbash API endpoint. Must be a trusted judge host
# (atbash.ai / www.atbash.ai under the default policy); other hosts need
# ATBASH_JUDGE_ENDPOINT_POLICY=self-hosted plus ATBASH_JUDGE_VERIFY_PUBKEY.
ATBASH_ENDPOINT=https://atbash.ai

# Set to self-hosted when using your own judge endpoint. Self-hosted judges
# require ATBASH_JUDGE_VERIFY_PUBKEY so the SDK can verify signed responses.
ATBASH_JUDGE_ENDPOINT_POLICY=default
ATBASH_JUDGE_VERIFY_PUBKEY=

# Fail closed when Atbash cannot be reached. Default: true.
ATBASH_ENFORCE_DECISION=true

# Emit verbose plugin logs. Default: false.
ATBASH_DEBUG=false

# Resolve the Atbash organization subscription/network. This lets the SDK
# choose the public or private chain for that org.
ATBASH_ORG_NAME=your-org-name

# Override where learned Hermes tool classifications are saved.
ATBASH_TOOL_MAP_PATH=$HOME/.config/atbash/hermes-tool-map.json

# Override where the persistent memory-tamper hash store is saved.
# Default: ~/.config/atbash/hermes-memory-hashes.json
ATBASH_MEMORY_HASH_PATH=$HOME/.config/atbash/hermes-memory-hashes.json

# Hermes provider and model — informational, passed through to Atbash context.
HERMES_PROVIDER=
HERMES_MODEL=
```

## Telemetry

The plugin initializes the Atbash Python SDK OpenTelemetry metrics with
`source="plugin:hermes"`, matching the pattern used by the OpenClaw plugin.
Telemetry is best-effort and never blocks guard registration or tool execution.

The Python SDK telemetry opt-out is file-based. To disable telemetry, create:

```bash
mkdir -p ~/.config/atbash
printf '{"enabled": false}\n' > ~/.config/atbash/telemetry.json
```

## Enable Or Check The Plugin

Hermes should discover installed Python packages that expose the
`hermes_agent.plugins` entry point.

Check whether Hermes sees the plugin:

```bash
hermes plugins list | grep atbash
```

If needed, enable it:

```bash
hermes plugins enable atbash-hermes-plugin
```

## Verify It Is Working

Start Hermes and ask it to do something that uses a tool, such as creating a
file or opening a website.

In another terminal, watch the Hermes log:

```bash
tail -f ~/.hermes/logs/agent.log
```

With `ATBASH_DEBUG=true`, you should see lines similar to:

```text
[atbash-hermes-plugin] registered pre_tool_call hook mode=fail-closed
Atbash pre_tool_call enter tool=...
Atbash verdict tool=... verdict=ALLOW reason=...
```

If Atbash blocks a tool call, Hermes receives a blocking response before the
tool executes.

## Docker

When running Hermes in Docker, mount your Hermes data directory and put the
Atbash key inside the mounted volume.

Example host layout:

```text
~/.hermes/
  .env
  atbash/
    guard-client-key
```

Example `~/.hermes/.env` for Docker:

```bash
ATBASH_KEY_PATH=/opt/data/atbash/guard-client-key
ATBASH_ENFORCE_DECISION=true
ATBASH_DEBUG=false
ATBASH_ORG_NAME=your-org-name
```

Run Hermes:

```bash
docker run --rm -it \
  -v ~/.hermes:/opt/data \
  nousresearch/hermes-agent
```

You can also pass variables directly:

```bash
docker run --rm -it \
  -v ~/.hermes:/opt/data \
  -e ATBASH_KEY_PATH=/opt/data/atbash/guard-client-key \
  -e ATBASH_ENFORCE_DECISION=true \
  -e ATBASH_DEBUG=false \
  -e ATBASH_ORG_NAME=your-org-name \
  nousresearch/hermes-agent
```

## Tool Classification

Hermes tool names can vary by version, installed plugins, and enabled skills.
The plugin ships with defaults for common tools and learns unseen tool names at
runtime.

Learned mappings are saved to:

```text
~/.config/atbash/hermes-tool-map.json
```

Set `ATBASH_TOOL_MAP_PATH` to override the location. The file persists across
Hermes sessions.

## Verdict Behavior

- `ALLOW` with `allow is True`: the tool proceeds. A missing `allow` flag is denied.
- `HOLD`: the tool is blocked with a review message.
- `BLOCK`, `DENY`, `REJECT`, `DISALLOW`: the tool is blocked.
- Atbash API error:
  - `ATBASH_ENFORCE_DECISION=true`: fail closed and block.
  - `ATBASH_ENFORCE_DECISION=false`: fail open and allow.
- Memory writes (`MEMORY.md`, `CLAUDE.md`, `memory/` paths) with `No verdict`: allowed
  and recorded as the new tamper baseline only when the judge marks the response as the
  AUDIT tier (`status: "logged"`) **and** the deployment opts in to monitor-only with
  `ATBASH_ALLOW_AUDIT_TIER=1`. Without the opt-in a `logged` answer is refused like any
  other unjudged one. Any other missing verdict — empty body, truncated
  payload, a proxy error served as 200, a compromised judge — is blocked under
  `ATBASH_ENFORCE_DECISION=true`; under `false` the write proceeds but is never
  recorded as the baseline, because it was not judged.

Atbash ships fail-closed on every tier. Setting `ATBASH_ENFORCE_DECISION=false`
inverts that for this agent: a judge outage becomes a silent allow, and the
governance layer stops governing for as long as it lasts. That is supported, but
record a written risk acceptance in the deployment's security summary before
turning it off, so the trade-off is auditable after an incident rather than
discovered during one. See decision 0003 (fail-closed default) in the dashboard
repo.

For `HOLD`, the user-facing block message is:

```text
Action flagged as a policy gap. The agent will not be jailed — please adjust your policy in the Atbash dashboard, then ask the agent to try again.
Reason: <original reason from judge>
Tool Call ID: <held judgment id, when provided by the SDK>
```

After the policy is adjusted in the dashboard, the SDK can query held-action status with
the judgment ID. The Hermes plugin blocks the original tool call and asks the
user to retry after the policy is updated, rather than automatically re-running the action.

## Guard Self-Protection And Memory Baselines

- **The guard's own state is not writable by the agent.** Write tools (`write`, `edit`,
  `multiedit`, `write_file`, `create_file`, `patch`, `apply_patch`, `str_replace_editor`,
  `fs_write`, `notebook_edit`, and anything the tool map classes as `filesystem_write`)
  are refused, before judging and in monitor mode too, when they target
  `~/.config/atbash`, the configured tool map, agent key and hash baseline, a dedicated
  key directory (such as `/opt/data/atbash`), the Hermes home's `.env`, `.op.env`,
  `config.yaml` and `SOUL.md` (`~/.hermes`, `$HERMES_HOME`, and `%LOCALAPPDATA%\hermes` on
  Windows), the Hermes install folder's `.env`, or the plugin's install directory. To
  change one of these (for example your persona in `SOUL.md`), edit the file yourself;
  the agent's write is refused even if you approve it inside Hermes. Paths are
  compared by file identity, so hard links and case variants count. Patch tools are also
  refused when their body names one of those paths.
- **A memory file with no trusted baseline is blocked, and nothing is sent anywhere.**
  A memory file is one of `AGENTS.md`, `AGENTS.override.md`, `CLAUDE.md`, `MEMORY.md`,
  `USER.md`, `DREAMS.md`, `.hermes.md`, `HERMES.md`, `.cursorrules` (by exact name, and on
  Windows also the spellings Windows opens as that file, such as `AGENTS.md.`), a
  `.cursor/rules/*.mdc` file, a `.md`/`.txt` document inside a `memory/` or `memories/`
  folder, or a file under `~/.claude/projects/` or `~/.openclaw/{workspace,memory}/`.
  Ordinary code such as `src/memory/alloc.rs` is not memory and is judged like any other
  file.
  Files the guard records itself (judged writes) are trusted. For files that existed
  before the guard (a repository's `AGENTS.md`), review them and trust them yourself,
  outside the agent:

  ```bash
  python -m atbash_hermes_plugin seed AGENTS.md memory/notes.md
  ```

  Run it from outside the repository you are trusting, with absolute paths (for example
  `cd ~ && python -m atbash_hermes_plugin seed /path/to/repo/AGENTS.md`): `python -m`
  puts the current folder first on Python's import path, so a hostile repository could
  otherwise shadow a standard module. The command itself never imports Hermes code.

  It prints the baseline file it wrote. That is the one the guard reads:
  `ATBASH_MEMORY_HASH_PATH` from your environment or from the Hermes home `.env`, else
  `~/.config/atbash/hermes-memory-hashes.json`. Pass `--store <file>` to choose it
  explicitly. If any file in the list cannot be read, nothing is trusted.

  The guard refuses agent tool calls that look like they run the seeder (the command,
  `seed_memory_baselines`, the baseline file's name, including quoted, variable-split or
  base64-encoded forms). This is best effort - see the known limit below - and it also
  refuses harmless agent calls that merely mention those names (editing these docs, a
  grep for the command).

  In Docker, point the baseline file at the mounted volume so trust survives restarts,
  e.g. `-e ATBASH_MEMORY_HASH_PATH=/opt/data/atbash/hermes-memory-hashes.json`, and run
  the seed command inside the container.
- **What the guard sees:** tool calls only. Hermes loads its context files (`AGENTS.md`,
  `.hermes.md`, `SOUL.md`, ...) into a new session's instructions without a tool call, so
  baselines protect the agent's tool reads, and the real control on those files is that
  agent writes to them are scanned and judged.
- **Known limit:** the baseline file (`~/.config/atbash/hermes-memory-hashes.json`) is
  plain JSON. The guard refuses the agent's file tools on it, but a shell command that
  rewrites a memory file and recomputes its SHA-256 is left to the judge. Closing that
  needs an authenticated baseline (a MAC) or state outside the agent's reach.

## Troubleshooting

If Hermes does not show the plugin:

```bash
hermes plugins list | grep atbash
python -m pip show atbash-hermes-plugin
```

Make sure the package was installed into the same Python environment that runs
Hermes.

If Atbash verdicts are not appearing in logs:

```bash
ATBASH_DEBUG=true
```

Then restart Hermes and watch:

```bash
tail -f ~/.hermes/logs/agent.log
```

If the plugin blocks everything with an unavailable-key or authentication error,
check:

```bash
echo "$ATBASH_KEY_PATH"
test -f "$ATBASH_KEY_PATH" && echo "key file exists"
```

If using Docker, remember that paths inside the container are different from
host paths. Prefer `/opt/data/...` paths for mounted Hermes data.
