All plugins

house-rules

House rules your agent can't forget. Refuses tool calls that match your rules (a command with flags such as git push --force or rm -r, or a regex on a tool argument such as a .env path) or that leave the folders you allow. The refusal tells the agent why and to ask you; you lift a rule for the current session with /rules unlock <n>. /rule turns plain words into a rule draft as YAML and replays it on past tool calls; you paste it into the profile config.yaml yourself, the plugin never writes it. Rules apply from the next session. For an agent that forgets or ignores instructions; it is not a security boundary (scripts, aliases and indirect effects are not seen). Disclosure — no network call of its own, no subprocess, no credentials, no telemetry, no background task. Hook pre_tool_call reads each tool call's name and arguments and refuses matches; a refusal notice (tool name and reason, never the arguments) is posted to the conversation with ctx.post_message. /rule makes one LLM call through ctx.llm.complete_structured with the user's words, and reads the profile's state.db read-only to replay the draft (other profiles' state.db only if the user picks them). Reads its settings with ctx.get_config and the profile config.yaml read-only. Writes nothing to disk.

AIverflowGeneralv0.2.0Updated 6 hours ago

About

House rules your agent can't forget. Refuses tool calls that match your rules (a command with flags such as git push --force or rm -r, or a regex on a tool argument such as a .env path) or that leave the folders you allow. The refusal tells the agent why and to ask you; you lift a rule for the current session with /rules unlock <n>. /rule turns plain words into a rule draft as YAML and replays it on past tool calls; you paste it into the profile config.yaml yourself, the plugin never writes it. Rules apply from the next session. For an agent that forgets or ignores instructions; it is not a security boundary (scripts, aliases and indirect effects are not seen). Disclosure — no network call of its own, no subprocess, no credentials, no telemetry, no background task. Hook pre_tool_call reads each tool call's name and arguments and refuses matches; a refusal notice (tool name and reason, never the arguments) is posted to the conversation with ctx.post_message. /rule makes one LLM call through ctx.llm.complete_structured with the user's words, and reads the profile's state.db read-only to replay the draft (other profiles' state.db only if the user picks them). Reads its settings with ctx.get_config and the profile config.yaml read-only. Writes nothing to disk.

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 of tests/e2e/test_e2e_hermes.py. Plain python -m pytest -q skips it.
  • Level B (manual only, real LLM, costs tokens): HR_PROFILE=work tests/e2e/run_live.sh --yes-run-live against 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.

More by AIverflow

2 plugins by AIverflow in the catalog.

AIverflowcacheguardWarns or blocks a large chat message sent after a pause, when the provider's prompt cache may have expired. Afterwards it shows how many tokens were reused or rebilled, and /cacheguard shows how often your cache was lost after pauses of each length. Modes (off, warn, block) are set per surface (terminal, desktop, gateway), and a model can have its own pause, size and mode. In block mode the message is not sent: the reply says so, and you send it with /retry or by sending the same text again. If the plugin fails, the message is sent. Disclosure — no network call, no LLM call, no subprocess, no credentials, no telemetry. Middleware llm_request reads every message of the request to hash the start of the chat (sha256, only the hash is kept) and removes cacheguard's own "message not sent" replies from what the model receives. Middleware llm_execution reads the token usage of each response; the provider call is Hermes's own next_call(), never repeated. Hook pre_llm_call returns a block for a large message after a pause. Hook transform_llm_output posts a result line with ctx.post_message and never changes the reply. Reads config.yaml (its own settings through ctx.get_config) and the environment variables HERMES_SINGLE_QUERY_SESSION, HERMES_KANBAN_TASK, HERMES_PLATFORM, HERMES_INTERACTIVE. Writes its settings with ctx.set_config (/cacheguard set and setup) and, under <HERMES_HOME>/cacheguard/, observations.jsonl (token counts, times and model name of calls after a pause; no message text, no session id) and one small json file per chat (times, token counts, hash). /cacheguard reset deletes them. Unattended runs (cron, kanban, subagents, curator, API server, webhooks) pass through untouched; one-shot runs (-Q, -z, --oneshot) are never blocked and show no line (a -Q run may be counted in the stats); with chat -q, block acts as warn. Private Hermes names: tools.approval_context._is_single_query_approval_context (a run with no screen only warns) and agent.usage_pricing.normalize_usage (read the cache token counts). If one is missing, the plugin says so in /cacheguard and degrades: it only warns, or result lines are missing.Communitytools4

Developer Mode

Explore built-in Hermes toolsets and actions.

HermesAgent

Developer?

Create a Hermes extension for Desktop or agent workflows.

Developer Section