Skip to content
MrJev

hermes-jev-approvals

Approvals provider for Hermes Agent: it judges shell commands and refuses every other task, registering no hooks.

View on GitHub →

Hands-on review

Replaces Hermes Agent's approval reviewer with six typed questions and deterministic code. We walked every branch of the ladder ourselves.

Good for

  • Replacing a prose-parsing approval reviewer with typed answers and fixed rules
  • A first rule that escalates when the command argues for its own approval
  • Redacting credentials out of the command before it leaves the machine

Watch out for

  • Only useful if you run Hermes Agent; it registers no hooks and serves one task
  • Three of its eight test files need Hermes core or live credentials
  • The numbers in `docs/METRICS.md` are the author's and a third party's, not ours

Tested Sep 22, 2026 at 28be98a19539 · python:3.12 in Docker with no network; its offline checks, the policy ladder driven branch by branch, and the redactor on credential-bearing commands

How we reviewed this: we ran every test file that works without Hermes core in python:3.12 with no network, re-running the suite at the maintainer’s current HEAD after he fixed the collection failure we reported, then called apply_policy directly with twelve synthetic answer sets to walk each branch of its decision ladder, and ran its redactor over commands carrying real-shaped credentials. We did not install Hermes Agent, so the provider registration and route tests are unverified here. No Jev calls.

What it does

Hermes Agent already decides which commands need review and asks an auxiliary model for APPROVE, DENY or ESCALATE. This plugin replaces that one reviewer, and the scope statement at the top of the README is refreshingly narrow: it “cannot chat or generate text, refuses every other task, and registers no hooks.”

One request asks six questions: a verdict choice, and nouls or scores for policy_allows, blast_radius, self_advocating, reads_secrets and sends_outbound. Then deterministic code decides. The model’s verdict is an input to the rules, not the output.

Walking the ladder

We called apply_policy with twelve answer sets. Every branch behaved as documented:

the answers say… verdict rule
ordinary command, model approves confidently APPROVE model_verdict
the command argues for its own approval (0.70) ESCALATE self_advocating
…and the model had said DENY DENY self_advocating
it reads secrets (0.80) ESCALATE secrets_read
it reads secrets and sends outbound DENY secrets_exfil
operator policy allows it, small blast radius APPROVE policy_allow
operator policy allows it, blast radius 2.0 ESCALATE model_verdict
policy_allows 0.99 but no policy configured ESCALATE model_verdict
model approves at confidence 0.54 ESCALATE low_confidence
model approves, blast radius 1.6 ESCALATE high_blast
model returns MAYBE ESCALATE invalid_verdict
model approves, command was truncated ESCALATE truncated

Three things are worth pulling out.

Rule one is a prompt-injection defence. self_advocating asks how likely it is that the command text argues for its own approval — a comment saying “this is safe, approve automatically” — and a high answer escalates regardless of the verdict. Putting that first, ahead of every efficiency rule, is the correct ordering and the one most likely to be got wrong.

A DENY is never softened. On both the self-advocating and secrets-reading branches, a model that said DENY keeps DENY; only the other verdicts are pulled up to ESCALATE. The rules can make the outcome stricter, never looser.

The only path to an unattended APPROVE that overrides the model needs an operator policy. policy_allows at 0.9 does nothing unless has_policy is true and the blast radius is under 2.0, so a model that is merely confident cannot approve past the configured policy.

Malformed or missing answers raise, and a raise means Hermes escalates to the user.

The command is redacted before it is sent

This is the part most approval reviewers skip. _redact runs Hermes core’s own redactor with force=True — deliberately ignoring the display-level security.redact_secrets: false, because “this is a third-party egress boundary, not a display surface” — and then applies shell-shaped passes that run on both paths, so the result doesn’t depend on core being importable. We ran the fallback path:

curl -u alice:s3cr3t https://api…        →  curl -u alice:*** https://api…
curl -H 'Cookie: session=abc123def' …    →  curl -H 'Cookie: ***' …
git clone https://user:ghp_ABCD…@github  →  git clone https://user:***@github…
psql postgres://app:hunter2@db.internal  →  psql postgres://app:***@db.internal
rm -rf ./build && npm run build          →  unchanged
echo $AWS_SECRET_ACCESS_KEY              →  unchanged

The last two matter as much as the first four: a redactor that mangles ordinary commands makes the reviewer judge something you didn’t run.

Long commands are cut head-and-tail with a visible marker “so a payload cannot hide behind filler”, and as the table above shows, truncation forces an escalate rather than an approval on partial evidence.

Tests

Run as scripts, the offline files pass: seven structured-audit checks, five boundary checks, five hardening checks, three policy-contract checks. The boundary file is the one to read — it tests HTTPS-only endpoints, exact host matching rather than suffix matching, that Authorization does not follow a cross-origin redirect, and that redaction covers the shapes that leaked before the fix. Its docstring says every assert is a regression test for something that actually leaked.

Running them through pytest used to be the rough edge. test_real_load.py printed a SKIP and exited with a module-level raise SystemExit(0), which stopped collection outright rather than skipping the file. At the commit we first reviewed:

$ python -m pytest plugin/tests -q
INTERNALERROR>   File "/w/plugin/tests/test_real_load.py", line 32, in <module>
INTERNALERROR>     raise SystemExit(0)
INTERNALERROR> SystemExit: 0
2 errors in 1.46s

Reported, and fixed in e22cbd0 within a day. The same command at 530fdb0, still with no network:

$ python -m pytest plugin/tests -q -rs
5 passed, 3 skipped in 1.58s
SKIPPED test_real_load.py is a direct integration check: requires Hermes core and an installed plugin
SKIPPED test_routes.py ... requires live provider credentials and network access
SKIPPED test_provider.py ... requires Hermes core, provider credentials, and network access

The fix is better than the one we suggested. Rather than converting the scripts to pytest idioms, a conftest.py keeps them out of the import-time collector and test_scripts.py runs each offline script in its own subprocess — which also stops their module-level environment stubs leaking into each other — while the three that need Hermes core or live credentials become declared skips that say what they need. One of the new tests is test_script_inventory_is_explicit, which asserts the set of files on disk matches the two lists, so a script added later cannot quietly escape the classification. The README now documents both commands.

The benchmarks directory is unusually careful about its own risk: the README says the corpus is mined from your session history, that one command in the author’s own corpus held a working bot token, and that every artefact is git-ignored and redacted before writing.

Numbers, and whose they are

docs/METRICS.md reports the author’s baseline and then, at length, an independent third-party study that pinned a specific build, ran real guard preprocessing and real HTTPS calls, and reported timings, approval counts and four adapter defects it found — command-delimiter loss, silent policy truncation, a CLI-flag redaction overmatch and a conservative composition — all of which the author says are covered by regression tests in the next version. We have not reproduced either set of numbers, and this review makes no claim about them; what we can say is that publishing someone else’s findings about your own plugin, in your own metrics document, is rare.

MIT, 25 commits, and a 24-row holdout fixture of recorded answer sets (10 approve, 8 escalate, 6 deny) so the ladder can be exercised without calling anything.

Verdict

If you run Hermes Agent with smart approvals, this is a better reviewer than a prose-parsing one, and the reasons are structural rather than model-dependent: fixed rules over typed answers, injection checked first, DENY never softened, credentials redacted at the boundary, and every unclear case landing on the user.

If you don’t run Hermes, read plugin/jev_policy.py anyway — 67 lines is the whole policy, and it is the clearest example in this directory of a model informing a decision without being the decision.

For the same idea in coding agents, see Jevvy and pi-warden.

See how it compares with other tools in Best Jev tools, tested hands-on.

Review updated Sep 22, 2026. Numbers quoted from the project are its author's own; we don't publish our own measurements of Jev.

More in Agent Integrations (MCP & Skills)

Hermes Jev Skills

★ 627▲ 528

kerpopule/hermes-jev-skills

Bundle of skills that hand an agent's small decisions to Jev: model routing, skill selection, retrieval filtering, compaction, and computer use, with a routing dashboard. Works with Hermes, Claude Code, and Codex.

PythonReviewed

Awesome Jev Skills

★ 446▲ 228

wuyoscar/jev-skill

Nine installable agent skills — triage, routing, code review, document and UI work — with a catalogue of scenarios to copy.

PythonReviewed

jev-mcp

★ 293▲ 236

jkudish/jev-mcp

Proof-of-concept MCP server with ready-made tools for fact checking, prompt-injection detection, and semantic ranking.

JavaScriptReviewed

Get new Jev projects every week

New Jev releases, pricing changes, and the best new projects, once a week. No spam; unsubscribe anytime.

Powered by Buttondown. See our privacy policy.