Last updated on

Hermes v0.19 Smart Approvals: Set Up 3 Security Gates Step by Step, with Commands


Hermes Agent v0.19.0 makes Smart Approvals the default behavior. In the past, when the Agent wanted to run a flagged command, it interrupted you for line-by-line confirmation. Now an independent LLM reviewer first classifies each command as safe, dangerous, or uncertain — and only the uncertain ones reach you.

That sounds great, but in production a wrong “auto-approve” can cost you a database, a production config, or a leaked API key. So this article breaks Smart Approvals into three gates you can actually deploy, letting you keep the automation without losing control. Every config key below is verified against the v0.19.0 source code and the official docs — keys floating around the internet like smart_approvals: true or deny_rules: with pattern:/reason: fields do not exist. The real schema is below.

For the full v0.19 picture, check our v0.19.0 release notes and the v0.19 feature overview.

Gate 1: LLM pre-review — auto-approve safe, auto-deny dangerous, escalate uncertain

This is the v0.19 default. Hermes no longer throws every command at you. An internal review model judges it first.

Decision logic:

  • Safe → auto-approved, no interruption
  • Dangerous → auto-denied with the reason logged
  • Uncertain → escalated to you for confirmation

Each command is reviewed individually — a previous approval does not give a pass to the next matching command. This eases approval fatigue, but introduces a new problem: the reviewer’s criteria are a black box to you. That’s why you need Gate 2 as a backstop.

By the way, the review model is configurable — the real key lives under auxiliary.approval (not approvals.review_model, which does not exist). You’ll see it in the full example below.

Gate 2: approvals.deny hard red lines — even yolo mode can’t cross them

The red-line config v0.19 gives you in config.yaml is approvals.deny: a list of fnmatch glob patterns that block matching terminal commands unconditionally. It takes precedence over --yolo, /yolo, and mode: off — in other words, it’s the user-editable counterpart to Hermes’ built-in hardline blocklist: “no matter how confident the Agent is, this command must never run.”

Example config:

# ~/.hermes/config.yaml
approvals:
  mode: smart        # smart | manual | off (smart is the default)
  deny:              # hard red lines: glob patterns that block unconditionally
    - "git push --force*"
    - "rm -rf /"
    - "*curl*|*sh*"
    - "kubectl delete namespace*"

Tips:

  1. List the operation categories you never want executed automatically — force-pushing to shared branches, recursive deletes, deleting prod namespaces, writing secrets via the CLI, and so on.
  2. Patterns are case-insensitive fnmatch globs, not regex. Quote them in YAML — a bare leading * is a parse error.
  3. On a match, the Agent gets an explicit BLOCKED message and is told not to retry or rephrase the command. There is no reason field in the deny list; if you want to document intent, use a YAML comment next to the pattern.
  4. Note: approvals.deny matches terminal commands (after normalization and deobfuscation, so tricks like r\m or git st""atus can’t dodge it), not tool-call strings — browser_*, file_* and similar tool calls are outside its scope.

Gate 3: human intervention with learnable feedback — /deny is more than a no

When the LLM pre-review can’t decide and the command reaches you, you normally have two options: approve or deny. v0.19 adds denial with a reason: /deny <reason> (/deny all <reason> denies all pending commands at once). The reason is relayed to the Agent and written into context, so next time it adjusts course instead of retrying the same command.

CLI / TUI example:

# Hermes wants to run: docker system prune -a -f
# You decide this isn't the moment, and deny with a reason
/deny this will wipe all images and could break other containers

# The reason lands in context; later attempts will avoid similar commands

If you just want to block it once, a reason-less deny works fine. But for long-term learning, make a habit of denying with a reason — a reasoned denial is feedback; a bare denial is a wall.

And if the Agent goes completely off the rails mid-sequence, /stop still ends the current run instantly. It’s been around since v0.3, but it pairs especially well with the new approval flow in v0.19.

Full three-gate config example

# ~/.hermes/config.yaml
approvals:
  mode: smart                   # smart | manual | off (smart is the default)
  timeout: 300                  # seconds to wait for your approve/deny; timeout = deny
  cron_mode: deny               # deny | approve — policy for unattended cron runs
  deny:                         # hard red lines: block unconditionally, even under yolo
    - "rm -rf /"
    - "git push --force*"
    - "kubectl delete namespace*"
    - "*curl*|*sh*"
  denial_breaker_threshold: 3   # hard-stop after N consecutive denials (0 disables)
  smart_policy: |               # optional: append custom rules to the reviewer LLM
    Always ESCALATE commands that modify anything under /etc.

# Review model (optional): defaults to auto; a fast, cheap model is recommended
auxiliary:
  approval:
    provider: auto              # auto | openrouter | nous | codex | custom
    model: ""                   # empty = provider default; e.g. gemini-flash, haiku-class

Points that are easy to miss:

  • denial_breaker_threshold (default 3): every time the reviewer denies a rephrased variant of the same command, another review call is burned. Once consecutive denials hit the threshold, the deny message escalates into a hard-stop instruction — the Agent must stop, report the blocked operation, and let you run it manually or /approve. Any approval resets the counter; set 0 to disable.
  • smart_policy: appends your own rules to the reviewer LLM’s system prompt (the trusted channel, never mixed with untrusted command text), so you can tighten or relax its judgment for your environment without editing code.
  • The config path is ~/.hermes/config.yaml (or your current Profile’s own config.yaml), not a project-level .hermes/config.yaml.

Restart Hermes after saving, then verify:

hermes config get approvals.mode
hermes config get approvals.deny

When do all three gates work together?

Scenario Gate 1: LLM pre-review Gate 2: approvals.deny Gate 3: Human
ls -la to inspect a directory auto-approved no match no interruption
rm -rf / matches deny blocked immediately, even under yolo no human needed
docker system prune -a judged uncertain no match escalated to you
Repeated variants of one denied command breaker threshold hit hard-stop; you run it manually

The key is that the gates complement each other: the LLM pre-review handles routine load, approvals.deny enforces hard constraints, and human judgment covers the gray zones. They don’t replace one another.

Advanced: different approval strictness per Profile

Every Hermes Profile has its own config directory (default Profile: ~/.hermes/config.yaml; named Profiles: ~/.hermes/profiles/<name>/config.yaml), so you don’t need a nested profiles: key in your config — just edit the Profile’s own file. For example: keep all three gates on the work Profile; use mode: off plus deny on a personal Profile; leave only the deny red lines on a CI/CD Profile. When you switch Profiles, Hermes loads the matching rule set automatically.

Common pitfalls and troubleshooting

  1. Deny rules have no effect: check the path — the user-level config is ~/.hermes/config.yaml (or your current Profile’s config.yaml), not a project-level .hermes/config.yaml. Also, changes only load after restarting Hermes/gateway — there is no hermes config reload command.
  2. LLM pre-review is too slow: point auxiliary.approval.model at a lightweight model (e.g. gemini-flash, haiku-class) instead of the default reviewer.
  3. Confirmations still pop up under yolo: the command was judged uncertain and matched no approvals.deny pattern. Add a pattern for it, or tighten the reviewer with smart_policy.
  4. YAML parse errors: globs starting with * must be quoted (e.g. "*curl*|*sh*"), otherwise the whole approvals block fails to parse and the config is ignored.

Summary

Hermes v0.19 Smart Approvals doesn’t hand authority to the LLM — it lets the LLM pre-filter while the final decision stays with you. Three gates:

  • Gate 1: LLM pre-review (approvals.mode: smart) handles routine commands
  • Gate 2: approvals.deny hard red lines, effective even in yolo mode
  • Gate 3: /deny <reason> and human confirmation keep learnable human intervention

Configured this way, your Agent is neither a nag that asks about everything nor a runaway that can do anything.

# Changes take effect after restarting Hermes (no reload command)
hermes config get approvals.mode    # verify the mode
hermes config get approvals.deny    # verify the red lines

Want more Hermes security and efficiency guides? Check out our earlier error handling and recovery guide and long-running tasks without getting stuck. To understand why Smart Approvals is the default, read approval fatigue: no more nodding at every step.