Local PII detection for Hermes Agent using <PERSON> and spaCy. No cloud service
or LLM is used for detection. This is a POC, not a confidentiality guarantee.
Enable the plugin first so Hermes provisions its managed Python environment,
then install the default English model, en_core_web_sm 3.8.0, separately in
that environment before starting a chat. For a local checkout and the default
Hermes home (~/.hermes):
mkdir -p "$HOME/.hermes/plugins"
# Run from the root of this checkout; skip if this link already exists.
ln -s "$PWD" "$HOME/.hermes/plugins/hermes-pii"
env -u UV_NATIVE_TLS UV_SYSTEM_CERTS=true hermes plugins enable hermes-pii
hermes plugins list --plain --no-bundledHermes admits
named PyPI dependencies only; the model wheel is hosted on GitHub and cannot be
installed through the plugin dependency installer. Use the interpreter from
Hermes' current managed environment (replace the path below). With the managed
layout under ~/.hermes, its path is recorded in
~/.hermes/installs/<install-id>/facts.json at
packages.venv.environment; append /bin/python. Select the record for the
Hermes installation you launch, rather than an old environment directory.
env -u UV_NATIVE_TLS UV_SYSTEM_CERTS=true uv pip install --python /path/to/hermes/environment/bin/python https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl
/path/to/hermes/environment/bin/python -c "import spacy; spacy.load('en_core_web_sm')"
/path/to/hermes/environment/bin/python -c "from hermes_pii.redactor import PresidioRedactor; result = PresidioRedactor().redact('Contact John Smith at john.smith@example.com.'); print(result); assert 'John Smith' not in result and 'john.smith@example.com' not in result"Restart Hermes after provisioning the model. Enabling plugins or updating Hermes
can create a new managed environment; check its current interpreter and repeat
model provisioning if needed. A successful plugin enable does not verify that
the NLP model is installed. The certificate environment variables above use the
system trust store for uv without disabling TLS verification.
The plugin does not download models at runtime. If the model is missing, redaction fails open and original text is sent to the provider.
For development:
uv venv --python 3.12 .venv
uv sync --frozen
# Install the host separately for tests using agent.context_references.
uv pip install --python .venv/bin/python 'hermes-agent>=0.19.0'
uv pip install --python .venv/bin/python https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whlFor a directory-plugin symlink in a Hermes profile, enable the plugin through Hermes so dependency admission provisions its managed environment:
env -u UV_NATIVE_TLS UV_SYSTEM_CERTS=true hermes -p <profile> plugins enable hermes-piiNamed profiles have separate plugin directories. Check the target profile with
hermes -p <profile> plugins list. If it does not show hermes-pii, link this
checkout into that profile's $HERMES_HOME/plugins/hermes-pii before enabling
it. Restart Hermes after installing, updating code, or changing settings. The
project .venv is separate from Hermes' managed environment. Do not install the
plugin both as a directory plugin and as an independently discovered pip entry
point.
If hermes plugins enable hermes-pii reports
hermes-agent is included as a workspace member, but is missing an entry in tool.uv.sources,
update this checkout and retry the enable command. Older revisions included
hermes-agent in the development dependency group. Hermes copies directory
plugins into its own uv workspace, where that declaration conflicts with the
host package. The dependency has been removed from the plugin metadata;
development and CI install Hermes separately for the integration tests that use
its context-reference preprocessing. Do not add hermes-agent back to this
plugin's dependency groups or add a workspace-only source to the standalone
checkout. Admission failure leaves the active environment and plugin enable
configuration unchanged.
Model licenses: en_core_web_sm is MIT. The optional it_core_news_sm model
is CC BY-NC-SA 3.0; verify that its terms fit your use. The spaCy library has a
separate license.
pre_llm_callprepares one cached detector. It does not analyze or append user text.llm_requestredacts user prompt text and tool-result text, and sanitizes supported image parts in those messages in a copy of the provider request.- Hermes continues its normal execution; original conversation history is unchanged.
Models load from installed packages or local directories. Runtime loading and analysis never download models. Email recognition uses a bundled, offline suffix snapshot. Overlapping detections are replaced across their full union.
Settings live under plugins.entries.hermes-pii.settings and are read once at
registration. Change them with hermes config set, then restart Hermes.
| Key | Default | Purpose |
|---|---|---|
language |
en |
Language supported by the selected model |
model_name |
en_core_web_sm |
Installed model name or local model directory |
score_threshold |
0.4 |
detection threshold |
The default model requires separate provisioning. To use Italian, first provision
it_core_news_sm 3.8.0 in the same Hermes environment. The plugin does not
install or download it at runtime. Then set both Italian settings from the
command line:
hermes config set plugins.entries.hermes-pii.settings.language it
hermes config set plugins.entries.hermes-pii.settings.model_name it_core_news_smRestart Hermes after changing settings. If the selected model is not installed, initialization fails and the plugin's fail-open policy passes original text through. Additional/custom models must be provisioned separately before use.
Models installed only into a managed virtual environment can disappear when Hermes creates a new environment. For a persistent local model, install the model wheel without dependencies into plugin data, then configure spaCy's model data directory directly. The plugin's managed environment supplies spaCy:
env -u UV_NATIVE_TLS UV_SYSTEM_CERTS=true uv pip install --no-deps \
--target "$HOME/.hermes/plugin-data/hermes-pii/models" \
https://github.com/explosion/spacy-models/releases/download/it_core_news_sm-3.8.0/it_core_news_sm-3.8.0-py3-none-any.whl
hermes config set plugins.entries.hermes-pii.settings.language it
hermes config set plugins.entries.hermes-pii.settings.model_name \
"$HOME/.hermes/plugin-data/hermes-pii/models/it_core_news_sm/it_core_news_sm-3.8.0"For a named profile, use that profile's plugin-data directory and run configuration
commands with hermes -p <profile>. Fully restart the desktop backend and gateway
after updating plugin code or configuration; opening a new chat alone does not
reload Python modules or settings held by an existing backend process.
From this checkout, test a new CLI session against the real configured provider:
hermes chat --cli --oneshot -Q --max-turns 4 --run-budget 90 \
-q 'Read tests/fixtures/pii-italian.pdf with read_file and quote its complete text. Do not use other tools.'The answer should contain <PERSON> and <PHONE_NUMBER> rather than the original
name and phone. An omission notice means inspection failed; it does not prove
successful detection. This CLI check calls the configured LLM and requires its
credentials. Use a new chat when retesting the desktop flow, since previous
assistant messages can already contain the unfiltered values. Original tool
previews remain local and may still show the source text.
- Chat Completions text in messages with
role: user. - Responses string input and text in input messages with
role: user. - Tool-result text in Chat
role: tool(and legacyrole: function) messages and Responsesfunction_call_outputitems, including PDF text extracted by Hermes'read_file. JSON results are parsed and string values are filtered; JSON keys, numbers, booleans, and null values retain their original values. - Inline/base64 raster images in Chat and Responses user messages and tool results. Tesseract OCR runs locally; detected text is covered with black rectangles before the image is sent.
- PDFs attached through Hermes'
pdf.attachflow, which renders pages to images, are covered by the same image path. - Word
.docxand Excel.xlsxattachments are converted locally to PDF pages, rasterized, and filtered through the same image/OCR path. Supported inputs are inline/base64 Chatfileand Responsesinput_fileparts with afilename, or explicit@file:references in user messages to files inside the Hermes process working directory. Other file text is covered when Hermes inlines it into a user message.
PDF file references and pdf.attach use different paths. Hermes' desktop
file.attach flow can submit a local file reference and let the agent call
read_file. Hermes extracts the PDF as text and returns it as a tool result.
The plugin filters that text before the next provider call. Word and Excel text
returned by tools follows the same path. In contrast, pdf.attach renders pages
as images, which pass through local OCR and pixel redaction. Neither path edits
the source PDF: local tool output and history retain the original content.
Tool-result text is never expanded as a local @file: reference.
Images must be inline base64 data that the plugin can inspect locally. Remote
image URLs, opaque file IDs, unsupported files, audio, and video parts are
replaced with an omission notice. Tesseract and trained data for the configured language must be installed
and visible on the Hermes process' PATH; pytesseract alone does not install
the Tesseract executable. On macOS, install the PDF renderer and English OCR
runtime with brew install poppler tesseract; install tesseract-lang as well
for languages beyond English. Image inspection is limited to 50 MiB and 40
megapixels, with a 20-second OCR timeout per image. Install these tools before
enabling the plugin, then restart Hermes.
Office files follow the same visual path as PDF attachments:
DOCX/XLSX → LibreOffice PDF → Poppler PNG pages → Tesseract + Presidio → blacked-out images
No additional Python packages are required for Office support. Install
LibreOffice in addition to the Poppler and Tesseract system tools already used
for PDF/image processing. uv sync and Hermes' plugin dependency installer do
not install these programs. All three commands,
soffice, pdftoppm, and tesseract, must be available on the Hermes process'
PATH. On macOS:
brew install --cask libreoffice
brew install poppler tesseract
# If soffice is not already on PATH:
export PATH="/Applications/LibreOffice.app/Contents/MacOS:$PATH"On Ubuntu:
sudo apt-get install libreoffice-writer libreoffice-calc poppler-utils tesseract-ocr tesseract-ocr-eng fonts-dejavu-coreVerify the commands from the same environment used to launch Hermes:
soffice --version
pdftoppm -v
tesseract --version
tesseract --list-langsThe OCR language data must match the plugin's configured language (eng for the
default English configuration). Install additional Tesseract language data when
using another language, alongside the corresponding spaCy model described above.
Restart Hermes, then attach a synthetic Office file or reference it in a user
message, for example Summarize @file:"documents/test.docx". Local references
are restricted to the process working directory and its descendants, including
after resolving symlinks. Place uploaded files in that workspace; references
outside it are omitted. The plugin replaces Hermes' binary-file preview stub
with filtered pages so it does not ask the model to read the original.
The provider receives only raster pages. Original Office files and local conversation history remain unchanged; no editable anonymized Office copy is produced. A model that accepts image input is required. Excel follows its print layout: hidden sheets, cells outside print areas, comments, metadata, and other content not rendered by LibreOffice are not sent. This is a visual preview, not structured spreadsheet extraction or inspection of every stored field.
Office input is limited to 20 MiB, 2,000 ZIP members, 100 MiB of expanded package content, and 20 pages. At most four Office references are expanded per text part. Conversion and rasterization each have a 60-second timeout; the existing 20-second OCR timeout applies per page. Oversized documents are omitted in full, rather than silently truncated. A conversion or OCR failure on any page omits the entire Office attachment. Temporary documents, PDFs, and page images are deleted after processing.
Only .docx and .xlsx are supported in this version. Legacy .doc/.xls,
macro-enabled, encrypted, externally linked, and embedded-object packages are
unsupported. Conversion uses an isolated LibreOffice profile with macro
security set to its highest level, macro execution disabled, and active
OLE/DDE content disabled. It is not an operating-system sandbox.
LibreOffice's command-line conversion documentation
describes the conversion interface used here.
For a named test profile, list profiles with hermes profile list. If needed,
link this checkout into that profile's plugin directory as described above.
Then run hermes -p <profile> plugins enable hermes-pii and
hermes -p <profile> chat --tui. Attach the PDF in the TUI. Ask Hermes to
transcribe a synthetic email in it; the expected result is that it cannot read
the blacked-out address. Check hermes -p <profile> plugins list to confirm the
plugin is enabled in that profile.
System/developer prompts, Responses instructions, assistant messages, and
tool-call arguments are preserved and never analyzed. Messages without a
supported role or a Responses function_call_output type are also preserved.
Protocol keys, roles, tool names, call IDs, schemas, metadata, and unlisted
provider fields are preserved. Within JSON tool-result bodies, string values
are filtered while keys and non-string scalar values are preserved.
Names, places, organizations, email, phones, IBANs, cards, and IP addresses are
recognized in supported text and OCR output; accuracy and false positives depend on context and OCR quality.
An OCR false negative can leave PII in an image, so this remains a best-effort
filter rather than a confidentiality guarantee.
User text is fail-open: a detector/import/model error returns the original text, including any PII. Every later operation retries; there is no permanent failure latch. Tool-result text is fail-closed: if local detection fails, the text is replaced with an omission notice and later calls retry. Attachments are fail-closed when inspection errors: the affected part is replaced with an omission notice. Warnings contain only the exception class, never input, exception messages, or tracebacks. OCR can miss PII without raising an error, so attachment sanitization is not a guarantee that every image was fully redacted.
Local history retains original values. No recovery mapping or value restoration is implemented. Auxiliary calls and middleware-bypassing paths have not been verified end-to-end. This plugin must not be treated as a mandatory privacy barrier. Use only synthetic data for development and smoke tests.
.venv/bin/python -m unittest discover -s tests -v
hermes plugins doctor . --ci
hermes plugins validate . --json
uv pip check --python .venv/bin/pythonTo include a real DOCX/XLSX conversion and OCR check, install the system tools above and run:
HERMES_PII_OFFICE_SMOKE=1 uv run --no-sync python -m unittest discover -s tests -vCI runs this check on Python 3.12; other matrix entries run the unit suite.
To check the desktop PDF file-read path with real Hermes extraction and the
Italian NLP model, provision it_core_news_sm in Hermes' current environment
and run from this checkout (adjust both paths for your installation):
HERMES_PII_PDF_SMOKE=1 PYTHONPATH="$HOME/.hermes/hermes-agent:$PWD" \
/path/to/hermes/environment/bin/python -m unittest discover -s tests -p test_tool_results.py -vFor the persistent model directory above, also set
HERMES_PII_ITALIAN_MODEL="$HOME/.hermes/plugin-data/hermes-pii/models/it_core_news_sm/it_core_news_sm-3.8.0"
when invoking the smoke test.
The bundled synthetic PDF contains Ciao sono Mario e il numero di telefono è 0797770099. The smoke test reads it through Hermes' read_file, filters both
Chat and Responses tool-result payloads, and asserts that the provider receives
<PERSON> and <PHONE_NUMBER> instead of the original values. No remote LLM
is called; socket connections are forbidden during detection.
Tests cover real offline text detection, provider-payload rewriting, protocol preservation, synthetic image redaction, Office rendering and reference handling, unsupported-media omission, preparation without duplicate analysis, sanitized failures, and transient-error recovery. The text integration test uses a preinstalled model and forbids socket connections during redaction. Synthetic OCR tests do not contact Tesseract or a remote LLM.