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:
- 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.
- Patterns are case-insensitive fnmatch globs, not regex. Quote them in YAML — a bare leading
*is a parse error. - On a match, the Agent gets an explicit BLOCKED message and is told not to retry or rephrase the command. There is no
reasonfield in the deny list; if you want to document intent, use a YAML comment next to the pattern. - Note:
approvals.denymatches terminal commands (after normalization and deobfuscation, so tricks liker\morgit st""atuscan’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(default3): 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; set0to 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 ownconfig.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
- Deny rules have no effect: check the path — the user-level config is
~/.hermes/config.yaml(or your current Profile’sconfig.yaml), not a project-level.hermes/config.yaml. Also, changes only load after restarting Hermes/gateway — there is nohermes config reloadcommand. - LLM pre-review is too slow: point
auxiliary.approval.modelat a lightweight model (e.g. gemini-flash, haiku-class) instead of the default reviewer. - Confirmations still pop up under yolo: the command was judged uncertain and matched no
approvals.denypattern. Add a pattern for it, or tighten the reviewer withsmart_policy. - YAML parse errors: globs starting with
*must be quoted (e.g."*curl*|*sh*"), otherwise the wholeapprovalsblock 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.denyhard 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.