Evaluation order
The evaluator is deterministic. For each call, the first check that decides wins.
| # | Check | Verdict | rule_id |
|---|---|---|---|
| 1 | Policy expired | BLOCK | policy-expired |
| 2 | Modifies protected paths (AgentsFence state, the plugin, any profile’s Hermes config/.env/auth.json/plugins/), or runs hermes plugins/config/hooks… or pip uninstall agentsfence |
BLOCK | self-protection |
| 3 | Matches a user prohibition | BLOCK | user-prohibition |
| 4 | Matches the deny list | BLOCK | policy-deny |
| — | Touches a secret file (~/.ssh, ~/.aws/credentials, ~/.hermes/.env, …) |
ASK_USER, combined at the end | secret-access |
| — | Any other state-changing shell command inside the Hermes home | ASK_USER, combined at the end | hermes-home-access |
| — | Writes into another profile (cross_profile) |
ASK_USER, combined at the end | cross-profile |
| — | Patch text names no files AgentsFence can parse | ASK_USER, combined at the end | path-unverifiable |
| 5 | A grant for this exact action (tool, capability, risk class, fingerprint) with uses left | ALLOW | approved-grant |
| 6 | Violates a tool_constraints recipient, domain or path limit |
BLOCK | recipient-not-authorized, domain-not-authorized, path-not-authorized |
| 6 | max_calls reached (including calls waiting on approval) |
BLOCK | call-limit-exceeded |
| 6 | Recipient can’t be determined under a recipient constraint | ASK_USER | recipient-unverifiable |
| 7 | Class ceiling off, or not listed (UNKNOWN tools: exact name in a manual policy only) | per evaluation.unauthorized |
unauthorized-<class> |
| 8 | Recipient (of a message, share, or an external write that names people, such as calendar attendees) not named by the user in a compiled policy | BLOCK | recipient-not-authorized |
| 8 | Compiled communication authorized but no recipients bound | ASK_USER | unbound-recipient |
| 9 | Domain outside allowed_external_domains |
BLOCK (writes) / ASK_USER (reads) | domain-not-authorized |
| 10 | URL looks like it carries data out | ASK_USER | exfil-url |
| 11 | Write or delete outside allowed_paths |
BLOCK | path-not-authorized |
| 12 | Outbound budget exhausted | BLOCK | external-write-budget |
| 13 | Matches requires_approval |
ASK_USER | requires-approval |
| 14 | Otherwise | ALLOW | policy-allow |
A manually written policy can deliberately allow a recipient domain when it lists no individual recipients. A compiled policy cannot use a browsing domain as recipient authority.
The “combined at the end” questions stand even if the policy would allow the call; a BLOCK from any check still wins over them. A grant for the exact action satisfies them, because you approved exactly that.
After evaluation:
- When Hermes would auto-approve, ASK_USER becomes BLOCK: always under the read-only fallback policy (
fallback-no-human), and otherwise whenstrict_yolois on (strict-yolo). - With
plugin_conflicts: strictand otherpre_tool_callplugins present, ASK_USER becomes BLOCK (plugin-conflict). - A call that carries only a task id unknown to AgentsFence is blocked before evaluation (
no-policy-for-task).
In the plugin, an internal error at any step returns BLOCK, and the audit log records an error event.
Why this order
Section titled “Why this order”- Guards first. Nothing, not even a policy you wrote by hand, lets the agent turn off its own fence.
- Hard stops are combined. A BLOCK from any of them wins, so a question from one check can never pre-empt a block from another.
- Prohibitions before grants. What you forbade can’t be approved away mid-task by clicking “yes”.
- Grants before class checks. An approval you gave for an exact action is honoured without asking again.
- Constraints before ceilings. An explicit “only John” is reported as the reason for a block, rather than a generic “outside authority”.