Skip to main content

agentguard-hermes

AgentGuard security monitoring plugin for Hermes Agent.

Forwards Hermes session, LLM, and tool-call events to an AgentGuard Timeplus stream for real-time observability, and synchronously blocks risky tool calls based on rule decisions from the AgentGuard server.

How it works

Two paths run depending on the hook type:

Async observation path (most hooks)

For every hook except pre_tool_call, the plugin POSTs the normalized event to Timeplus via urllib and the callback returns immediately — Hermes never waits.

Synchronous hold-and-wait path (pre_tool_call only)

When AGENTGUARD_HOLDS_ENABLED=true, pre_tool_call:

  1. POSTs the event to AgentGuard's /api/holds endpoint synchronously via urllib.request.urlopen(timeout=600). The endpoint ingests the event itself, so the plugin does NOT also call its async ingest path for this hook.
  2. AgentGuard's backend ingests the event, then waits up to ~500 ms for any rule's materialized view to detect a match (also checks for any pre-existing open threats from earlier in the session).
  3. Backend resolves to one of three outcomes based on the matched rule's block_policy:
    • allow (no rule fired, or rule policy is log_only) → returned immediately
    • block (rule policy is auto_block) → returned immediately with reason
    • hold (rule policy is hold) → opens a hold record, long-polls for human Approve/Deny in the AgentGuard UI (up to 5 minutes), returns the human's decision
  4. Plugin returns {"action": "block", "message": "..."} from the callback on block (Hermes short-circuits the tool call), or None on allow (Hermes runs the tool normally).

The agent's hot path is paused for the duration of the hold — no LLM tokens are burned waiting for human review.

If AGENTGUARD_HOLDS_ENABLED is false, pre_tool_call falls back to the async observation path and never blocks. Useful for toggling blocking on/off during testing without uninstalling the plugin.

Hermes timeout note: unlike Claude Code (600s hook cap) and OpenClaw (configurable approval timeout), Hermes provides no hook timeout and no abort signal. The plugin self-imposes urllib.request.urlopen(timeout=600) so a stuck /api/holds request can't hang Hermes forever. The backend's hold-timeout ceiling is 540 s, leaving 60 s of margin under the plugin timeout.

Installation

pip install agentguard-hermes-plugin
agentguard-hermes install

This copies the plugin into ~/.hermes/plugins/agentguard/ where Hermes auto-loads it on startup.

Configuration

Set environment variables before starting Hermes:

Async event ingest (Hermes → Timeplus)

Variable Default Description
AGENTGUARD_TIMEPLUS_URL http://localhost:3218 Timeplus base URL. timeplusd HTTP port (3218) in timeplusd mode; the web console / REST API base (port 8000, or its HTTPS URL behind an ingress) in rest mode
AGENTGUARD_INGEST_API timeplusd timeplusd = POST straight to timeplusd; rest = the Timeplus REST API (/{workspace}/api/v1beta2/streams/{stream}/ingest) for deployments where port 3218 is not exposed. Same username/password (Basic auth) in both modes
AGENTGUARD_WORKSPACE default REST API workspace id in the URL path. Rarely needed
AGENTGUARD_DATABASE "ag" Timeplus database owning the AgentGuard streams. Matches the AgentGuard Timeplus app's db_name: ag.
AGENTGUARD_USERNAME proton Timeplus username
AGENTGUARD_PASSWORD (empty) Timeplus password
AGENTGUARD_STREAM agentguard_hook_events Target Timeplus stream

Identity

Variable Default Description
AGENTGUARD_AGENT_ID hostname Identifier for this agent instance
AGENTGUARD_DEPLOYMENT_ID local Deployment environment tag
AGENTGUARD_DEPLOYMENT_NAME Local Dev Human-readable deployment name

Synchronous tool blocking (pre_tool_call → AgentGuard /api/holds)

Variable Default Description
AGENTGUARD_URL http://localhost:8080 AgentGuard server base URL — where pre_tool_call POSTs its hold request
AGENTGUARD_HOLDS_FAIL_POLICY deny What to return when /api/holds is unreachable: deny blocks the tool call, allow lets it proceed
AGENTGUARD_HOLDS_ENABLED false When false, pre_tool_call reverts to async observation-only mode (no blocking, event still ingested)

Server-side prerequisite — Timeplus service credentials

The plugin's synchronous /api/holds call hits the AgentGuard server with no user session cookie (server-to-server). The AgentGuard server in turn needs Timeplus credentials to ingest the event and query rules. You must set TIMEPLUS_USER and TIMEPLUS_PASSWORD env vars on the AgentGuard process — these are the canonical service credentials for plugin endpoints.

Without these, /api/holds returns:

HTTP 503
{"error":"service credentials not configured: set TIMEPLUS_USER and TIMEPLUS_PASSWORD env vars on the AgentGuard server"}

For the standard docker-compose.yaml setup these are pre-set to proton / timeplus@t+. Edit them if your wizard credentials differ, then docker compose up -d --build agentguard.

Example

# Async ingest — self-hosted, timeplusd reachable
export AGENTGUARD_TIMEPLUS_URL=http://timeplus.example.com:3218
export AGENTGUARD_DATABASE=ag
export AGENTGUARD_USERNAME=proton
export AGENTGUARD_PASSWORD=secret
# …or via the Timeplus REST API when port 3218 is not exposed (same credentials)
# export AGENTGUARD_INGEST_API=rest
# export AGENTGUARD_TIMEPLUS_URL=http://timeplus.example.com:8000

# Identity
export AGENTGUARD_DEPLOYMENT_ID=production
export AGENTGUARD_DEPLOYMENT_NAME="Production Hermes"

# Sync tool blocking
export AGENTGUARD_URL=http://agentguard.example.com:8080
export AGENTGUARD_HOLDS_FAIL_POLICY=deny
export AGENTGUARD_HOLDS_ENABLED=true

hermes

What gets captured

Every Hermes hook event is forwarded to the agentguard_hook_events stream:

Hermes hook Sent as Description
on_session_start on_session_start New session begins
on_session_end conversation_end Single chat turn completed
on_session_finalize on_session_end Session fully torn down (CLI exit, /reset) — also fires POST /api/holds/_abandon-session to clean up any pending hold
on_session_reset on_session_reset Session rotated via /new
pre_llm_call pre_llm_call Before each LLM turn
post_llm_call post_llm_call After each LLM turn
pre_api_request pre_api_request Before each raw API call (token metrics)
post_api_request post_api_request After each raw API call
pre_tool_call pre_tool_call Before each tool execution — synchronous when AGENTGUARD_HOLDS_ENABLED=true: ingest happens via /api/holds (not the async path), and the callback returns {"action": "block", "message": ...} on deny
post_tool_call post_tool_call After each tool execution

conversation_history is stripped from all events before ingestion — it is unbounded and contains no information not already available from the individual turn events.

Verifying the synchronous hold flow

To exercise blocking end-to-end:

  1. Open the AgentGuard UI at http://localhost:8080, log in, and install a rule that matches Hermes tool calls (e.g. Privilege Guard for shell-like tools).
  2. Set its block_policy to hold on the rule detail page (/rules/:id → Block Policy panel).
  3. In Hermes, ask the agent to run a matching tool call.
  4. In the AgentGuard UI you should see within ~1 s:
    • A toast notification bottom-right: "Hold pending: <tool> on <agent>"
    • A pending-holds badge on the Threats sidebar entry
    • The threat detail page shows an Approve / Deny banner with the rule message and a truncated args_summary
  5. Click Approve or Deny → Hermes resumes (or rejects) the tool call within ~1 s. On deny, Hermes feeds the deny message back to the LLM as a tool error.
  6. Audit trail: every hold lifecycle row is in the agentguard_holds stream:
    curl -s -X POST http://localhost:3218/proton/v1/query \
      -u proton:'timeplus@t+' -H 'Content-Type: application/json' \
      -d '{"query":"SELECT hold_id, tool_name, status, decided_by, decided_at FROM table(mv_holds_current) ORDER BY created_at DESC LIMIT 10"}'
    

If the plugin reports AgentGuard unreachable — backend returned 503, the AgentGuard server is missing service credentials — see Server-side prerequisite.

Custom install path

agentguard-hermes install --hermes-dir /path/to/hermes/data

Manual installation (Makefile / Docker)

If you run Hermes via the provided Docker Compose setup in agents/hermes/:

make configure   # copies plugin into .hermes/plugins/agentguard/
make cli         # start Hermes CLI with AgentGuard env vars pre-set
make start       # start Hermes gateway + web dashboard

Publishing to PyPI

pip install hatch
cd agents/hermes/agentguard-plugin
hatch build     # produces dist/agentguard_hermes_plugin-*.whl
hatch publish

Metadata

Release files for agentguard-hermes-plugin 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agentguard-hermes-plugin 0.4.0
File Size Uploaded Provenance Actions
agentguard_hermes_plugin-0.4.0.tar.gz 12.0 kB Without provenance Details for agentguard_hermes_plugin-0.4.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for agentguard-hermes-plugin 0.4.0
File Interpreter ABI Platform Actions
agentguard_hermes_plugin-0.4.0-py3-none-any.whl Python 3 none any Details for agentguard_hermes_plugin-0.4.0-py3-none-any.whl

Total release size: 23.7 kB

Release files / agentguard_hermes_plugin-0.4.0.tar.gz

Download URL agentguard_hermes_plugin-0.4.0.tar.gz
Size 12.0 kB
Tags Source
SHA-256 checksum
How to use checksums
dffa0820a87dfc81cc1462b645b99e3032237264f079791f8c8f4e720b5a4f70
BLAKE2b-256 checksum
How to use checksums
b11f943009a584095c83a8cca287f9911c610531351ee8ecf890efea7e1b7ada
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.9

Release files / agentguard_hermes_plugin-0.4.0-py3-none-any.whl

Download URL agentguard_hermes_plugin-0.4.0-py3-none-any.whl
Size 11.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4e411a22550123409b0d658e2137144997e032c1c82233a43e38bbf613b75945
BLAKE2b-256 checksum
How to use checksums
7b2b1ad5b830a6e4c26530806b52fa75c7bde96b4e0ee434f580c3d442df97fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.9

Security for agentguard-hermes-plugin 0.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page