House Rules
Refuse Hermes tool calls that match a rule (a command with flags, or a regex), or that leave the folders you allow. A rule can use action: stop to refuse later covered calls in the same turn after a match. A refusal is also posted to the current conversation with the tool name and the reason (never the arguments); if delivery fails, /rules lists the session's recent undelivered refusals. You write the rules in the profile config.yaml; they apply from the next session.
It targets an agent that forgets or ignores instructions, not a malicious one. It is not a security boundary. See documentation.md for full behavior and limits.
Demo video (70 s): docs/demo.mp4
Install
Tested with Hermes Agent v0.22.7 (f7067f4757).
Use a separate Hermes profile, not your personal one. Create one once (here work; pick your own name):
hermes profile create work --no-skills --no-alias
From this project folder, copy and enable the plugin:
PROFILE=work
DEST="$HOME/.hermes/profiles/$PROFILE/plugins/house-rules"
mkdir -p "$DEST"
cp plugin.yaml __init__.py commands.py history.py rules.py runtime.py "$DEST/"
hermes -p "$PROFILE" plugins validate "$DEST" --install-deps
hermes -p "$PROFILE" plugins doctor house-rules --ci
hermes -p "$PROFILE" plugins enable house-rules
If a gateway already serves the profile, it keeps the old plugin code until it reloads plugins (for example hermes -p "$PROFILE" plugins disable house-rules then enable).
Configure
Add this to ~/.hermes/profiles/$PROFILE/config.yaml (here ~/.hermes/profiles/work/config.yaml):
plugins:
entries:
house-rules:
settings:
folders: # optional; without it, file tools and terminal paths are not limited by folder
read_write: [/tmp] # the session working directory is always read-write; file tools must use absolute paths
read_only: [~/docs]
rules:
- command: git push
flags: [-f, --force, +]
message: "No force push"
- command: [npm install, npm i, npm add, npm ci]
message: "Use pnpm"
- command: rm
flags: [-r, -R, --recursive]
message: "No recursive rm"
action: stop # optional; defaults to block
- tool: terminal # advanced: a regex, when command and flags cannot say it
regex: '\b(?:curl|wget)\b[^;&|\n]*\|\s*(?:ba|z)?sh\b'
message: "No download piped to a shell"
A command rule names a program and the words after it (git push); flags is optional and the rule matches when one of them follows. It sees the usual spellings (git -C x push -f, sudo git push --force-with-lease, git push origin +main, CI=1 npm install, rm -fr x) and not pnpm install, git rm -r or echo git push -f. It does not see aliases, scripts, variables, sudo -u user git ..., or other programs with the same effect (find -delete, python -c ...). A rule can also take workdir to apply only under one folder; see documentation.md. Use regex only for what command cannot say, and avoid nested quantifiers such as ^(a+)+$: they can exceed the 30 s hook deadline.
With folders, a terminal command that names a path outside the folders (or redirects with >, >> or tee outside the working directory and read_write) is refused, and the message names the path. It reads the command text only, so scripts and paths built at run time are not seen, and a word like grep "/api/" can be refused by mistake. See documentation.md. For real isolation, use the Hermes Docker terminal backend.
Start a new session. /rules shows what the session loaded, numbered and locked/unlocked. /rules unlock <n|folders> turns off one rule or the whole folder limit for this session only (/rules lock <n|folders> turns it back on); nothing is written, and the next session starts from the config. /rule <instruction> asks the model for a rule; /rule init reads the project context files and proposes scoped rules with source phrases. Both print YAML for you to review and paste; neither writes anything. /rule <instruction> also replays the rule on your past tool calls (read-only, last 2,000 calls, 2 s) and says how many it would have refused; other profiles are read only if you pick them when asked.
If rules or folders sit next to settings instead of inside it, or settings has an unknown key, the session refuses every call and the message shows the fix (corrected YAML is shown only, never written). /rules reports the same problem.
End-to-end tests
- Level A (no LLM, a few seconds): the real plugin inside real Hermes, in a temporary
HERMES_HOME. Needs the Hermes venv python; the exact command is in the docstring oftests/e2e/test_e2e_hermes.py. Plainpython -m pytest -qskips it. - Level B (manual only, real LLM, costs tokens):
HR_PROFILE=work tests/e2e/run_live.sh --yes-run-liveagainst the profile you name. Never run by pytest.
License
MIT, see LICENSE.
README from the reviewed commit 54194a4; it updates when the author re-pins.

AIverflow