Catalog PyPI
PACKAGE EXPLORERView in registry

agentguard-hermes-plugin

PyPI

AgentGuard security monitoring plugin for Hermes Agent

agentguardai-agenthermesmonitoringobservabilitysecurity
Versions collected1
Direct dependencies0From release 0.4.0
Last observed20 Oct, 22:06 UTC

README

Version 0.4.0 documentation

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)

VariableDefaultDescription
AGENTGUARD_TIMEPLUS_URLhttp://localhost:3218Timeplus 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_APItimeplusdtimeplusd = 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_WORKSPACEdefaultREST 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_USERNAMEprotonTimeplus username
AGENTGUARD_PASSWORD(empty)Timeplus password
AGENTGUARD_STREAMagentguard_hook_eventsTarget Timeplus stream

Identity

VariableDefaultDescription
AGENTGUARD_AGENT_IDhostnameIdentifier for this agent instance
AGENTGUARD_DEPLOYMENT_IDlocalDeployment environment tag
AGENTGUARD_DEPLOYMENT_NAMELocal DevHuman-readable deployment name

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

VariableDefaultDescription
AGENTGUARD_URLhttp://localhost:8080AgentGuard server base URL — where pre_tool_call POSTs its hold request
AGENTGUARD_HOLDS_FAIL_POLICYdenyWhat to return when /api/holds is unreachable: deny blocks the tool call, allow lets it proceed
AGENTGUARD_HOLDS_ENABLEDfalseWhen 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 hookSent asDescription
on_session_starton_session_startNew session begins
on_session_endconversation_endSingle chat turn completed
on_session_finalizeon_session_endSession fully torn down (CLI exit, /reset) — also fires POST /api/holds/_abandon-session to clean up any pending hold
on_session_reseton_session_resetSession rotated via /new
pre_llm_callpre_llm_callBefore each LLM turn
post_llm_callpost_llm_callAfter each LLM turn
pre_api_requestpre_api_requestBefore each raw API call (token metrics)
post_api_requestpost_api_requestAfter each raw API call
pre_tool_callpre_tool_callBefore 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_callpost_tool_callAfter 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

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.4.018 Sept 20260

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
timeplus/agentguard

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 22:06 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:f716fefeb48370e9d769cb8646b057335d228b30372766a1cad1b2f918f704c6