refusal
A Hermes plugin that detects when a model refuses a request, or deflects to an educational alternative, and retries the turn on your configured fallback model.
Some primary models decline legitimate work, such as security research, penetration-testing help and malware analysis. Instead of leaving you with a dead-end reply, refusal reports the refusal to Hermes so the turn continues on the next model in your fallback chain.
Features
- Refusal detection. Direct refusals ("I can't help with that"), policy invocations ("this violates my guidelines") and deflections ("I can't do X, but I can explain Y defensively").
- Few false positives. Tool-calling responses, code blocks, partial answers ("I can't do X, but here's Y") and idioms such as "can't help but" are all treated as genuine answers.
- Public APIs only. It uses the
llm_executionmiddleware and never patches Hermes core. - No dependencies, no network calls. Detection is local and rule-based.
Requirements
- Hermes
>= 0.22.1(therequires_hermesfloor inplugin.yaml) - Python
>= 3.10 - At least one entry in
fallback_models. Without one, a detected refusal ends the turn (see Behavior and disclosures).
Installation
hermes plugins install refusal
hermes plugins enable refusal
To install from a local checkout:
ln -s "$(pwd)" ~/.hermes/plugins/refusal
hermes plugins enable refusal
Check that it loaded:
hermes plugins list
hermes plugins capabilities refusal
Configuration
The plugin has no settings of its own. Configure the fallback chain in ~/.hermes/config.yaml:
model: openrouter/google/gemini-2.0-flash-001
fallback_models:
- anthropic/claude-3-5-haiku-20241022
How it works
- The plugin registers an
llm_executionmiddleware and calls the model as usual. - Responses that contain tool calls (OpenAI
tool_callsor Anthropictool_use) are returned untouched. - For text responses,
RefusalDetectorchecks the prose, ignoring fenced code blocks, against refusal, deflection and partial-answer patterns. - When a refusal is detected, the plugin sets
finish_reasontocontent_filter(Anthropic:stop_reasontorefusal). - Hermes handles
content_filterthrough its content-policy path and activates the next fallback model.
Hermes does not let plugins register custom failover reasons, so a detected refusal appears under Hermes's own content_policy_blocked reason.
Behavior and disclosures
- Every turn is inspected. The middleware reads each model response before the agent loop uses it. Nothing is stored or sent anywhere.
- Responses are modified. A detected refusal changes the response metadata (
finish_reason,stop_reason) so that Hermes triggers failover. - Fallback costs tokens. Hermes resends the full conversation to the fallback model, and the fallback provider bills for it.
- No fallback configured. Hermes replaces the response with its standard notice,
⚠️ The model declined to respond to this request (safety refusal).A false positive in this setup discards a valid answer, so configure at least one fallback model. - The plugin makes no network calls, runs no subprocesses, starts no background processes, reads nothing outside its own package and stores no credentials.
Library usage
The detector works without Hermes:
from refusal import detect, is_refusal
result = detect("I cannot assist with executing cyberattacks.")
result.is_refusal # True
result.kind # "refusal"
result.reason # "direct_refusal"
is_refusal("chmod 600 ~/.ssh/id_rsa") # False
reason is one of direct_refusal, policy_refusal or off_topic_deflection. kind is refusal or deflection.
Development
python -m pip install -e . pytest
pytest
Validate the packaging against the Hermes catalog rules:
hermes plugins validate . --install-deps
hermes plugins doctor . --ci
Layout
plugin.yaml Hermes manifest
__init__.py Plugin entry point (exports register)
refusal/
detector.py RefusalDetector and DetectionResult
patterns.py Compiled refusal, deflection and partial-answer patterns
plugin.py register() and the llm_execution middleware
tests/ Detector and fallback tests