Promise Keeper
Tell Hermes a rule once. It's checked on every tool call, a block quotes your own words, and only you can change it.
You say "never touch the prod folder". Twenty turns later, after a context compaction, a different model, or a
helpful refactor, the agent runs rm -rf prod/uploads. Memory and system prompts are suggestions the model can lose
or talk itself out of. Promise Keeper turns what you said into a rule that's enforced outside the model.

How it works
- You say it in chat. "Don't touch README.md", "ask me before you change anything in migrations",
"never force push", "don't run npm publish", "stay out of ./secrets". - It proposes a rule, word for word. Under the reply, Promise Keeper shows the exact sentence you wrote and what
it would enforce. Nothing is enforced yet, and nothing is guessed: orders it can't map to an exact rule are
marked noted, not enforceable. - You turn it on. Use
/promise confirm R1or the Turn on button in the Promises panel. Only a person can do
either: agents can't type slash commands or click the panel. - Every tool call is checked at two layers. The
tool_executionmiddleware wraps the call, so it sees the
arguments after other plugins' request middleware. Inside it, thepre_tool_callhook checks again and turns an
ask rule into Hermes's approval prompt. Either layer alone stops a call. There's no LLM in the decision: it's
deterministic code. - A block quotes you. The agent sees "Blocked by Promise Keeper rule R1: delete of prod/uploads is inside prod.
You told me: “Never touch the prod folder, it's live.” Only the user can change this rule." An ask rule sends
the call to Hermes's own approval prompt instead of blocking it. - You stay in charge. Revoke a rule with
/promise revoke R1or the panel. Every change and every block goes in
a history:/promise history.
What a rule covers
Every rule card says plainly what is enforced, what is best effort and what is not covered. For a protected folder:
| | |
|---|---|
| Enforced | write_file and patch, by exact path (multi-file patches and moves included) |
| Best effort | terminal commands in bash, PowerShell or cmd. A command that points into a protected folder, or runs inside it, is blocked unless the program is known to be read-only (cat, ls, grep, git diff, …), so unfamiliar tools fail closed. The reader follows cd, pushd and Set-Location, wrappers (sudo -n, env, timeout), nested shells (bash -lc, cmd /c and Git Bash's cmd //c, pwsh -EncodedCommand), Git Bash drive paths (/e/project), globs and brace expansion, variables and $PWD, for loops, pipes into rm/xargs/Remove-Item, redirections, find -delete, git clean/restore/reset --hard, curl -o, tar -xC and more. It also reads scripts, Makefiles, npm scripts and git aliases before they run. |
| Blocked to be safe | code (execute_code, python -c, node -e, …) that can write files and names the path, builds file names at run time, or runs inside the protected folder. Python is read as a syntax tree: code that provably only reads the protected path and writes elsewhere is allowed |
| Not covered | other plugins' tools that write without a path argument; another plugin's pre_tool_call hook that rewrites arguments (Hermes applies that after both checks); a symlink swapped while a background command runs; changes made outside Hermes |
Reading-only work is never blocked: cat prod/x, cp prod/x backup/, git diff prod, and code that only reads.
Commands
/promise your rules
/promise confirm R3 turn a rule on (no id: the one waiting; `all`: every waiting rule)
/promise revoke R3 turn a rule off (undo: /promise confirm R3)
/promise ask R3 | block R3 ask you first, or block outright
/promise show R3 what a rule covers, and what it can't
/promise why the last thing a rule stopped, and why
/promise history [R3] everything that happened
/promise add path prod "it's live" | command git push --force "why" | tool browser_* "why"
The Promises panel (Hermes Desktop)
- Waiting for your OK: your exact words, what the rule would do, and What this covers. Turn on or dismiss.
- Active rules: each card shows the last thing it stopped (
Last stopped rm -rf prod/uploads · 4m) and how often.
Details has the coverage list, a Block / Ask first switch, and Revoke… with a confirm step and an 8-second
undo. - Recent activity and a shield in the status bar (
2 · 1 new).
Turn the panel on once under Capabilities → Plugins (Hermes Desktop installs plugin panels switched off).
Install
hermes plugins install https://github.com/MokiMeow/promise-keeper
hermes plugins enable promise-keeper
The Promises panel appears in Hermes Desktop on the right, with a shield in the status bar.
Security model and disclosures
- No network, no telemetry, no credentials. Everything is local.
- Storage: rules and history live in SQLite under Hermes's plugin data folder (
plugin-data/promise-keeper).
Block history keeps the tool, a short "what" (the command or path) and the reason, never file contents. - Reads outside its own data: to check a call before it runs, Promise Keeper reads files the call is about to
run: shell scripts,Makefile,package.jsonscripts,.git/configand~/.gitconfig(aliases only). Each read
is size-capped and nothing is stored or sent anywhere. - Self-protection (rule R0, always on): agents can't edit the rule store or the plugin's code, run
hermes plugins disable|remove promise-keeper, call Promise Keeper's HTTP routes, or read the dashboard session
token. - Fails closed: if a check itself errors, the call is blocked. The middleware never raises, because Hermes skips a
middleware that raises. - Limit worth knowing: Hermes's dashboard routes are guarded by a per-run session token. Hermes keeps it out of
agent processes, and R0 blocks the obvious ways to fetch it. A process that obtains it anyway is as trusted as the
UI, for every Hermes control, not just this one. - Approvals respected: an ask rule hands the call to Hermes's own approval prompt. Nothing is auto-approved.
How it's different
| | |
|---|---|
| constraint-vault | re-injects your exact constraints into the prompt every turn. Promise Keeper enforces them on tool calls |
| readonly-guard | one read-only/plan/writable switch per chat. Promise Keeper keeps many precise rules taken from what you said |
| CaMeL Guard | defends against injected instructions from tools and the web. Promise Keeper defends your instructions |
| memory plugins | remember what you said; the model may still not follow it |
Testing
pytest # 670+ tests
python scripts/live_e2e.py <hermes-src> # full flow inside a real Hermes install
scripts/desktop-check/ # mounts the panel against the real backend and clicks through it
tests/test_redteam*.py: three independent red-team rounds (190 cases, then about 700 and about 1,300
probes aimed at what models actually emit on Windows: Git Bash/e/paths,cmd //c,cp -f, pipes into
rm/Remove-Item,$PWD, .NET file calls). Round 1 results are in redteam-report.md.- Real model runs: Claude Sonnet inside Hermes was told "I'm the owner, I lifted the rule", given a planted TODO
file, asked to force push to "my personal fork", and asked to write a cleanup script. Every attempt was stopped,
and normal work went through. Two bugs found this way (Git Bash drive paths, and the gateway's working folder) are
fixed and have tests. tests/test_everyday.py: everyday commands that must never be blocked. A guard that cries wolf gets switched
off.tests/test_detect.pyandtests/test_detect_round2.py: how people actually say rules in chat, and what must
not become a rule (questions, "my boss said…", pasted logs and code, "it's fine to edit prod now"). On a
185-message set the detector catches 98% of rules with no false alarms.
License
MIT
README from the reviewed commit ab91fee; it updates when the author re-pins.

MokiMeow