How it works
The principle
Section titled “The principle”The agent’s plan is not trusted as policy. The user’s intent is compiled into an independent authorization contract. Hermes may replan however it wants, as long as every action stays inside that contract.
Plans are written by the model, and the model is the party that prompt injection manipulates. So AgentsFence never takes authority from the plan. Authority comes from you, once, when the task starts.
Stage 1: compile (pre_llm_call)
Section titled “Stage 1: compile (pre_llm_call)”your message │ strip quoted / pasted / forwarded text ▼compiler (OpenRouter · Hermes model · offline rules) │ JSON draft, schema-validated ── on failure ──► read-only fallback ▼ │hardening (verbatim evidence · literal targets · prohibitions) │ ▼ ▼TaskPolicy ◄─────────────────────────────────────────────────────────┘- Only your message is used. Tool output, conversation history and subagent goals are never passed to the compiler.
- Quoted material is removed. Fenced code blocks,
>quotes, forwarded or replied email tails, pasted-content tags and long quoted strings are data you’re showing the agent. They grant nothing. - Structured output. The compiler returns JSON that is validated against a schema. Invalid output is retried once, then the fallback is used.
- Hardening only removes authority:
- every consequential permission needs a verbatim quote from your instruction that contains a request for that kind of action: a clause that opens with the action’s verb. “Summarize my notes” can’t justify a delete, “don’t email bob” can’t justify an email, and “list save and delete commands” remains informational. A summary may also request a concrete output path or email address; for other actions, use a separate sentence;
- evidence is checked per risk class: a request to send a message doesn’t authorize a CRM update, even though both are “external writes”;
- recipients must be the object of a send or share request in your words (“the email from alice@…” names a sender, not a recipient); a domain named for browsing cannot authorize other send recipients. Domains and paths must appear as whole tokens, and roots such as
/or your home directory are refused; - “don’t send / only read / don’t buy / never delete” become prohibitions that override the compiler. They are read from your whole message, even if a long message is truncated for the compiler;
- wildcard grants, and grants for tools AgentsFence can’t classify, are dropped.
The result is stored per Hermes session, and a short note tells the model a policy is in force.
Later messages in the same session are compiled only if they could change authority (“now email it to john@example.com”, “don’t send anything”). Newer explicit statements win, and “only … / … instead” replaces earlier recipients rather than adding to them. In a shared chat, only the person who started the task can widen it.
Stage 2: enforce (pre_tool_call)
Section titled “Stage 2: enforce (pre_tool_call)”For each tool call, before it executes:
- Classify the call by what it can do: a capability (
email.send) and a risk class (COMMUNICATION). Arguments count:browser_consolewith a JavaScriptexpressionis code execution, and a multi-filepatchis checked against every file it names. Recipients, URLs and paths are extracted from the arguments. - Evaluate it deterministically against the policy. See Evaluation order.
- Answer:
- ALLOW: Hermes runs the tool.
- BLOCK: the tool never runs. The model receives the reason and is told not to work around it.
- ASK_USER: Hermes’ approval prompt asks you. See Approvals & grants.
No LLM is involved in stage 2. It is plain code over the policy and the arguments, so it can’t be talked out of a decision. A BLOCK always wins over a question.
Why replanning stays free
Section titled “Why replanning stays free”A policy describes an envelope (which kinds of effects, toward whom, how many times), not a list of steps. A READ-only policy allows every read tool, including ones nobody anticipated. The agent can switch from Gmail to Drive to the web without asking. It can’t send, delete, share or pay unless you said so.
What happens when things go wrong
Section titled “What happens when things go wrong”| Situation | Result |
|---|---|
AgentsFence raises an internal error in pre_tool_call |
BLOCK (Hermes would otherwise run the tool) |
| AgentsFence can’t start (bad config) | a stub hook blocks every tool call |
Another plugin returns approve for a call AgentsFence blocks |
AgentsFence moves its hook first when it loads and at every turn, so its BLOCK is what Hermes acts on, from the first tool call on |
| Compiler unavailable or returns garbage | read-only fallback, prohibitions preserved; its questions become blocks if Hermes would auto-approve |
| No policy exists for the session | read-only fallback (or BLOCK with missing_policy: block) |
| A call carries only a task id nobody knows | BLOCK |
| ASK_USER with no human present | Hermes’ approval gate blocks |
| Policy expired (default 12h) | BLOCK until your next message starts a fresh policy |