Hermes AI Usage Monitor

August 27, 2026 · View on GitHub

CI CodeQL Secret scan OpenSSF Scorecard License

A read-only extension for Hermes Agent that separates three facts people often blur together:

  1. Official provider quota — only when Hermes can obtain a provider-backed snapshot.
  2. Local Hermes usage — token and call counters recorded in Hermes state.db.
  3. Unavailable data — displayed honestly instead of converted from a guessed allowance.

The plugin supports the native Hermes Desktop app and the Hermes Web Dashboard. It never reads prompt or message content.

Current plugin version: v0.7.5.

Merging or publishing v0.7.5 distributes artifacts only; neither action deploys, restarts, upgrades, or changes any Hermes installation. Installation or migration remains a separate, explicit operator action.

Tested baseline (not a minimum-support claim): Hermes v0.20.5, local source authority 981101239a064c020a9d18fc3b1060ae306934ed, tested 2026-08-25.

Features

  • account-quota windows, remaining percentage, and reset time for providers supported by Hermes core (openai-codex, Anthropic OAuth, and OpenRouter);
  • Desktop status-bar indicator and detailed page;
  • Web Dashboard tab at /ai-usage;
  • active-session token and context counters in Hermes Desktop;
  • selectable 24-hour, 7-day, 30-day, and 90-day container-aware UTC chart that retains every returned hourly/daily bucket and scrolls only when buckets reach their minimum width;
  • selected-period token composition with exact and percentage breakdowns, a multi-color linear timeline, and hatched reasoning visibly nested within output rather than double counted;
  • session history with provider, model, safe surface/workload enums, strict profile slug, validated duration/active state, calls, five visible token-consumption bands, and a short reference that can be searched in retained Hermes logs;
  • all-profile history initially selected in Desktop and Web Dashboard, with an explicit Current-profile selector and per-profile totals that disclose incomplete or truncated reads;
  • separate non-cache-read, cache-read, and secondary raw totals across summaries, profile attribution, period composition, and recent sessions;
  • a compact, keyboard-scrollable per-profile table showing five data rows at a time;
  • fixed-coordinate quota scales whose visible red, orange, yellow, and green portion represents remaining allowance, with a text legend and the used/reset details retained;
  • French and English UI;
  • no independent credential handling, browser storage, analytics, or third-party scripts.

Important semantic boundary

For openai-codex, the percentage is the Codex allowance attached to the ChatGPT subscription. It is not a universal meter for ordinary ChatGPT conversations. Provider quota and token counters are deliberately displayed separately: model choice, cache, reasoning, tools, images, service tier, and rolling windows make a direct conversion misleading.

The UI uses one neutral client-side metric: non_cache_read_tokens = input_tokens + output_tokens + cache_write_tokens, after each component is normalized to a finite non-negative bound. Cache-read tokens are shown separately. Raw total is the secondary sum of non-cache-read and cache-read tokens. These local counters do not represent spend, billing, provider quota, or provider allocation.

Security posture

The repository is intentionally small and adds zero plugin runtime dependencies: it reuses Hermes' FastAPI, SQLite state, provider adapters, Desktop SDK, and Dashboard SDK.

Key controls:

  • provider credentials remain inside Hermes adapters;
  • account snapshot cache is scoped by Hermes home/profile and provider;
  • per-key single-flight prevents duplicate concurrent provider snapshot fetches and uses bounded waiter liveness with cleanup even when an owner aborts;
  • SQLite URI mode=ro plus PRAGMA query_only=ON;
  • static parameterized SQL; no mutation statements;
  • response allowlisting and bounded display strings;
  • exact session-surface enums and fail-closed optional-schema handling; raw source, title, paths, chat metadata, and lineage identifiers are never returned;
  • no complete session identifiers, prompt content, messages, tool payloads, or raw exception text; only bounded log-searchable session suffixes are returned;
  • no innerHTML, eval, browser storage, custom auth headers, or direct browser fetch;
  • CI with locked development dependencies, Ruff, Bandit, pip-audit, CodeQL, Gitleaks, dependency review, zizmor, and OpenSSF Scorecard;
  • GitHub Actions pinned to immutable commit SHAs.

Read SECURITY.md, the threat model, and the privacy model before deploying in a shared environment.

Repository layout

plugin.yaml                              Hermes plugin manifest (`ai-usage-monitor`)
__init__.py                              Inert agent entry point
desktop/plugin.js                         Native Hermes Desktop extension
dashboard/manifest.json                   Web Dashboard manifest
dashboard/dist/index.js                   Web Dashboard UI bundle
dashboard/dist/style.css                  Theme-aware dashboard styles
dashboard/plugin_api.py                   Read-only FastAPI router
tests/                                    Backend and frontend smoke tests
scripts/security_invariants.py             Privacy/security regression gate
docs/                                     Architecture, installation, privacy, threat model

Installation

See docs/INSTALLATION.md for complete installation, verification, upgrade, and removal instructions.

Before the primary Git install, select the profile with hermes profile show PROFILE_NAME, copy its exact home, and run this fail-closed preflight. Any legacy standalone Desktop tree or non-Git/manual unified destination must use Legacy split-tree migration or deterministic manual fallback instead; do not delete either tree or unrelated plugins.

export HERMES_HOME="/exact/Path/from-profile-show"
test "$HERMES_HOME" = "$(realpath -e -- "$HERMES_HOME")"
test -f "$HERMES_HOME/config.yaml"
PLUGINS_ROOT="$HERMES_HOME/plugins"
DESKTOP_PLUGINS_ROOT="$HERMES_HOME/desktop-plugins"
for PLUGIN_ROOT in "$PLUGINS_ROOT" "$DESKTOP_PLUGINS_ROOT"; do
  if [ -L "$PLUGIN_ROOT" ]; then
    printf '%s\n' "Symlinked plugin root found; use the migration procedure." >&2
    exit 1
  fi
  if [ -e "$PLUGIN_ROOT" ]; then
    test -d "$PLUGIN_ROOT" &&
      test "$PLUGIN_ROOT" = "$(realpath -e -- "$PLUGIN_ROOT")" || {
        printf '%s\n' "Plugin root is not a canonical directory; use the migration procedure." >&2
        exit 1
      }
  fi
done
LEGACY_DESKTOP="$HERMES_HOME/desktop-plugins/ai-usage-monitor"
UNIFIED="$HERMES_HOME/plugins/ai-usage-monitor"
if [ -e "$LEGACY_DESKTOP" ] || [ -L "$LEGACY_DESKTOP" ]; then
  printf '%s\n' "Legacy Desktop tree found; use the migration procedure." >&2
  exit 1
fi
if [ -e "$UNIFIED" ] || [ -L "$UNIFIED" ]; then
  test -d "$UNIFIED" && test ! -L "$UNIFIED" || exit 1
  test "$(git -C "$UNIFIED" rev-parse --show-toplevel)" = "$(realpath -e -- "$UNIFIED")" || {
    printf '%s\n' "Existing unified tree is not its own Git clone; use the migration procedure." >&2
    exit 1
  }
fi

The complete guide then resolves v0.7.5 to an exact commit, installs it with hermes plugins install ... --enable, and verifies the installed clone's HEAD. That command enables the agent/Dashboard half only. For both a fresh Git install and a v0.7.2/v0.7.3 split-tree migration, open Hermes Desktop Settings → Plugins, find AI Usage Monitor under Desktop plugins, choose Rescan if it is not inventoried yet, and confirm its switch is off. If an upgrade shows it on, switch it off first: the old standalone copy's enablement state is not authority for the new unified root. Then switch it on explicitly. No Desktop reload or restart is required; the tested host activates Desktop contributions live. Only then verify the AI Usage page and status-bar indicator.

Git clone installation is not covered by archive SHA256SUMS or attestation. The manual migration verifies those controls, requires positive unified-host-contract evidence, swaps the two historical trees transactionally, preserves rollback evidence and configuration, and never removes unrelated plugins.

A pinned exact-SHA install is intentionally immutable: hermes plugins update ai-usage-monitor is expected to fail closed rather than move it. For a Git version transition, preserve the old exact SHA and recovery prerequisites before removal, resolve the published version to a new exact 40-character commit SHA, disable and remove only ai-usage-monitor with supported commands, then install that SHA with the scanner enabled. Use a bounded interactive review of every CAUTION finding and stop on BLOCK. Never use --force or disable the scanner. Split-tree/manual users must use the transactional release installer, not this Git lifecycle. This transition must not delete or edit unrelated plugins or configuration. The complete commands and rollback path are in docs/INSTALLATION.md.

Development

python3 -m venv .venv
. .venv/bin/activate
python -m pip install --require-hashes -r requirements-dev.txt
make check

The plugin's production runtime is provided by Hermes. requirements-dev.txt contains only pinned CI and review tools.

API

Hermes mounts the router under /api/plugins/ai-usage-monitor:

  • GET /health
  • GET /snapshot?provider=auto
  • GET /history?days=7&limit=200&bucket_start=<UTC epoch>&scope=current (days is bounded to 1–90; bucket_start is optional and must be a UTC bucket boundary within the requested range, with the immediately preceding bucket also accepted as clock grace)

Hermes Dashboard authentication protects these routes. The plugin does not create another auth mechanism and should not be exposed independently. session_ref is a 12-character suffix (extended on collisions), not the complete session ID; it can be searched in local Hermes logs only while the corresponding logs are retained. Bucket-specific results remain capped at 200 rows and expose row_count plus rows_truncated so the UI never implies that a partial list is complete. History rows expose surface, workload_type, profile, duration_seconds, and is_active. The legacy source field is only an alias of the safe surface enum. The backend API retains scope=current as its request default and reads only the active profile when scope is omitted. Desktop and Web Dashboard initially request scope=all, and their selector preserves the chosen scope across period changes, bucket selection, and refresh. Hermes documents the Dashboard as a machine-level management surface that can manage every local profile. Accordingly, every authenticated Dashboard principal must be treated as a trusted machine operator, and first-party Desktop/Web clients initially select All profiles. Shared low-privilege Dashboard access is unsupported. Operators requiring distinct exposure must use separate authentication and --isolated per-profile Dashboard servers. See the official Dashboard documentation and official profile documentation. The plugin does not invent a separate per-profile ACL. All-profile responses include partial, totals_complete, profile_failures, profiles_considered, profiles_succeeded, and profiles_truncated. Failures contain only profile slugs and fixed codes. Duplicate physical databases are read once. If distinct databases contain an equal full session ID, every affected database is excluded and the response is marked partial because exact ownership cannot be proven. Provider quota remains account-level/shared and is never apportioned to profiles. Raw-volume bands are fixed by raw total: Low below 10k, Moderate below 50k, Elevated below 100k, High below 250k, and Extreme at 250k or above. They appear only beside the explicitly labelled secondary raw total, and visible text accompanies color.

Roadmap

See ROADMAP.md. Per-turn attribution will only be labelled exact when provider data and concurrency permit it; otherwise it will be labelled estimated or confounded. The project will not derive an official quota percentage from guessed local token allowances.

Contributing and security

Licensed under Apache-2.0.