Skip to content

Design Decisions by Version

  • 9 hooks registered (no subagent_stop yet).
  • SQLite schema v1: runs, llm_calls, tool_calls.
  • post_api_request identified as the primary token hook (not post_llm_call).
  • Cron ID anchored-regex extraction (R4) decided upfront.
  • Budget engine added. The A1-A4 audit (see The Hermes Plugin Model above) drove the design: tool-gate is the only real enforcement mechanism.
  • Schema v2 migration: added cache_read_tokens, cache_write_tokens, reasoning_tokens, estimated on llm_calls; parent_session_id, estimated_llm_calls on runs.
  • Subagent stop hook added. Decision: record proxy tool_call row only, never attempt to attribute tokens (would double-count).
  • budget_alerts table (schema v3 migration): anti-spam ledger for one-time-per-window alerts.
  • Dashboard: stdlib HTTP server, zero external deps. --host 0.0.0.0 flag for headless deployments, with explicit warning on non-loopback binding.
  • Provider label = verbatim: stored as-is from post_api_request.provider. NOT normalized. Everything routed through OpenRouter shows as “openrouter”. This was a deliberate decision — the routing path is meaningful observability data.
  • Auto-setup on first load: if pricing.yaml or budget.yaml are missing, the plugin generates defaults non-interactively. Guarded by HERMES_TELEMETRY_NO_SETUP=1 for CI/tests.
  • /setup slash command: three modes — auto (fetches OpenRouter), minimal (built-in defaults only), skip.
  • Pricing auto-refresh from OpenRouter API. PricingSource ABC for extensibility. _meta.auto_models to detect and preserve manual overrides.

v0.3.1 — Gemini pricing + orphan-run fix

Section titled “v0.3.1 — Gemini pricing + orphan-run fix”
  • Gemini 3.x/2.5 family added to _DEFAULT_PRICING and _PREFIX_PRICING.
  • Deprecated Gemini entries removed: gemini-1.5-*, gemini-2.0-* (sunset).
  • Generic gemini prefix removed from _PREFIX_PRICING (was Flash 1.5 pricing for any unknown Gemini variant — silently mis-priced new models by ~6.5×). Unknown Gemini variants now surface as unknown-model warnings.
  • _ensure_run_row lazy insert (orphan-run fix): sessions that join a running gateway after the plugin loads never receive on_session_start. All subsequent UPDATE calls were silent no-ops. Fix: INSERT OR IGNORE stub row before every UPDATE in record_llm_call and end_run.

v0.4.0 — Google AI pricing source + symmetric lookup

Section titled “v0.4.0 — Google AI pricing source + symmetric lookup”
  • GoogleAISource in pricing_refresh.py: direct Google AI Studio pricing as a constant table (no structured API). LAST_VERIFIED date for manual quarterly refresh. Registered alongside OpenRouterSource.
  • Symmetric Google lookup in pricing._lookup_base: gemini-X and google/gemini-X resolve to the same entry. Two-pass: try the literal ID first, then _google_alt_form(model_lc). This is deliberately Google-specific — other provider prefixes carry distinct pricing semantics and must not be stripped.

v0.4.1 — Provider-aware pricing + NVIDIA NIM seeds

Section titled “v0.4.1 — Provider-aware pricing + NVIDIA NIM seeds”
  • Provider-aware lookup guard (issue #24): estimate_cost gains an optional provider arg threaded through _resolve_pricing → _lookup_base → _lookup_form. _source_eligible rejects an _source: openrouter entry for a non-OpenRouter call so the OpenRouter rate never leaks onto a Nous/NIM call. provider="" preserves the old provider-blind behaviour (backward compatible). See Provider-aware lookup guard.
  • _subscription tag (Option A for Nous flat-sub): declared $0, kept distinct from a lookup miss and from _estimated_price. Tracked in _meta.subscription_models.
  • NVIDIA NIM seeds (issue #12 Phase 1): five Nemotron models added to _DEFAULT_PRICING (source-neutral, immune to OpenRouter sync). The same-id OpenRouter collision is resolved by the guard falling through to the seed. Phase 2 (NVIDIANIMPricingSource API auto-sync) was deliberately dropped — NIM bills against account tier, not a uniform public per-token list, so the seed table is the durable answer.
  • _SCHEMA_VERSION bump 3→4: the _migrate_v4 migration (cache tokens on runs) shipped without bumping the constant, leaving test_schema_idempotent red. Fixed here as part of documenting schema v4.

v0.5.0 — Free→paid model transition alert (issue #16, Block A)

Section titled “v0.5.0 — Free→paid model transition alert (issue #16, Block A)”
  • known_free_models table (schema v5): records every (model, provider) pair seen at explicit $0 cost. INSERT OR IGNORE — append-only, never deleted.
  • is_explicitly_priced(model, provider) in pricing.py: distinguishes a genuine $0 entry from an unknown-model $0.00 fallback. Without this, every unrecognized model would be flagged as “free” and generate spurious alerts.
  • record_free_model / is_known_free_model in db.py: write and read the known_free_models table respectively. is_known_free_model checks both the exact (model, provider) row and a wildcard (model, '') row.
  • Backfill on load: register() seeds known_free_models with provider='' wildcard rows for all explicitly-$0 models from the pricing table. INSERT OR IGNORE, runs every load. Covers pre-v5 installs automatically.
  • _pending_free_paid_alerts (module-level dict in __init__.py): maps session_id → (model, cost). Populated by post_api_request when a previously-free model is seen at cost > 0. Consumed (popped) by pre_llm_call.
  • pre_llm_call now handles two injection paths: budget soft-alerts (anti-spam via budget_alerts table) and free→paid one-shot alert (pop-on-consume from _pending_free_paid_alerts). Both inject via the {"context": "..."} return.
  • 253 tests (was 233): test_init.py gained free→paid detection, queueing, injection, and backfill tests; test_db.py and test_pricing.py gained corresponding unit tests.

v0.5.1 — Free→paid id-change handling (issue #32)

Section titled “v0.5.1 — Free→paid id-change handling (issue #32)”
  • is_free_tier_transition(model, provider) in db.py: reverse-looks-up a stored <id>:free row when a provider moves a model to paid under a different id (dropped :free suffix or suffixed paid id at a token boundary). Wired into post_api_request as is_known_free_model(...) OR is_free_tier_transition(...).
  • :free suffix → $0 rule in _lookup_form: any …:free id resolves to an explicit $0 before the prefix scan (see Lookup priority chain). This both (a) stops a seeded model’s :free variant from being mis-billed at its paid rate via prefix, and (b) records the :free id as known-free with no estimated-price warning — so the transition alert seeds itself with no user config. This replaced the earlier design’s manual-_subscription requirement. It also surfaced and fixed a latent bug: nvidia/nemotron-3-super-120b-a12b:free (a pre-existing 0.4.1 seed) had been resolving to the paid $0.09/$0.45 rate.
  • nvidia/nemotron-3-ultra paid seed in _DEFAULT_PRICING (OpenRouter rate, NIM-direct pending). Catches the bare id (exact) and the …-550b-a55b form (prefix), making cost > 0 after the 2026-06-18 promo end — which fires the alert. The free …:free forms are unaffected (handled by the suffix rule above).
  • 263 tests (was 253): test_db.py gained 7 is_free_tier_transition cases; test_pricing.py gained the ultra paid-seed + :free-suffix → $0 (bare and OpenRouter-long-form), super:free regression, and explicit-override cases.

v0.8.0 — Mixture-of-Agents (MoA) attribution

Section titled “v0.8.0 — Mixture-of-Agents (MoA) attribution”
  • moa.py (new module): resolves the provider="moa" virtual-provider preset to its aggregator’s real provider/model via hermes_cli.moa_config.resolve_moa_preset. Defensive — swallows every error and falls back to the raw hook values.
  • post_api_request now detects MoA calls and records them under the aggregator’s real provider/model (fixing the provider_assumed misfire) and tags the row with the preset name.
  • Schema v10 (_migrate_v10): llm_calls.moa_preset + runs.moa_calls.
  • Reference-model tokens remain uncaptured (auxiliary call_llm fires no hooks) — documented as a Known Limitation; MoA cost is a lower bound and is flagged as such in /stats and both dashboards.
  • Verified against agent/moa_loop.py, agent/auxiliary_client.py, agent/agent_init.py, agent/conversation_loop.py, hermes_cli/moa_config.py.

v0.8.0 — Agent intelligence (efficiency · smells · forecast, PR #40)

Section titled “v0.8.0 — Agent intelligence (efficiency · smells · forecast, PR #40)”
  • smell_detector.py (new module): five read-only anti-pattern heuristics over existing telemetry. See § Agent Intelligence.
  • db.efficiency_runs + stats._efficiency_block + plugin dashboard /efficiency: per-session efficiency score (0-100). Error penalty keyed on the real run statuses error/interrupted (source-verified against __init__.py session end — not the subagent-only failed/cancelled).
  • db.daily_spend_series + budget.burn_rate_projection + /budget forecast + standalone /api/budget/forecast: moving-window burn-rate projection toward the configured limit; read-only, no state mutation.
  • No schema change — all three are pure reads over runs/llm_calls/ tool_calls, so there is no _migrate_vN.
  • Run-status strings confirmed against __init__.py session-end logic, whose on_session_end kwargs are sourced from agent/conversation_loop.py.

hermes-telemetry is an independent community plugin for Hermes Agent. It is not affiliated with or endorsed by Nous Research.