Design Decisions by Version
v0.1.0 — Initial scaffold
Section titled “v0.1.0 — Initial scaffold”- 9 hooks registered (no
subagent_stopyet). - SQLite schema v1:
runs,llm_calls,tool_calls. post_api_requestidentified as the primary token hook (notpost_llm_call).- Cron ID anchored-regex extraction (R4) decided upfront.
v0.2.0 — Budget + Dashboard
Section titled “v0.2.0 — Budget + Dashboard”- 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,estimatedonllm_calls;parent_session_id,estimated_llm_callsonruns. - Subagent stop hook added. Decision: record proxy
tool_callrow only, never attempt to attribute tokens (would double-count). budget_alertstable (schema v3 migration): anti-spam ledger for one-time-per-window alerts.- Dashboard: stdlib HTTP server, zero external deps.
--host 0.0.0.0flag 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.
v0.3.0 — Setup wizard
Section titled “v0.3.0 — Setup wizard”- Auto-setup on first load: if
pricing.yamlorbudget.yamlare missing, the plugin generates defaults non-interactively. Guarded byHERMES_TELEMETRY_NO_SETUP=1for CI/tests. /setupslash command: three modes —auto(fetches OpenRouter),minimal(built-in defaults only),skip.- Pricing auto-refresh from OpenRouter API.
PricingSourceABC for extensibility._meta.auto_modelsto 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_PRICINGand_PREFIX_PRICING. - Deprecated Gemini entries removed:
gemini-1.5-*,gemini-2.0-*(sunset). - Generic
geminiprefix 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 asunknown-modelwarnings. _ensure_run_rowlazy insert (orphan-run fix): sessions that join a running gateway after the plugin loads never receiveon_session_start. All subsequent UPDATE calls were silent no-ops. Fix:INSERT OR IGNOREstub row before every UPDATE inrecord_llm_callandend_run.
v0.4.0 — Google AI pricing source + symmetric lookup
Section titled “v0.4.0 — Google AI pricing source + symmetric lookup”GoogleAISourceinpricing_refresh.py: direct Google AI Studio pricing as a constant table (no structured API).LAST_VERIFIEDdate for manual quarterly refresh. Registered alongsideOpenRouterSource.- Symmetric Google lookup in
pricing._lookup_base:gemini-Xandgoogle/gemini-Xresolve 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_costgains an optionalproviderarg threaded through_resolve_pricing→_lookup_base→_lookup_form._source_eligiblerejects an_source: openrouterentry 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. _subscriptiontag (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 (NVIDIANIMPricingSourceAPI 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_VERSIONbump 3→4: the_migrate_v4migration (cache tokens onruns) shipped without bumping the constant, leavingtest_schema_idempotentred. 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_modelstable (schema v5): records every(model, provider)pair seen at explicit $0 cost. INSERT OR IGNORE — append-only, never deleted.is_explicitly_priced(model, provider)inpricing.py: distinguishes a genuine $0 entry from an unknown-model$0.00fallback. Without this, every unrecognized model would be flagged as “free” and generate spurious alerts.record_free_model/is_known_free_modelindb.py: write and read theknown_free_modelstable respectively.is_known_free_modelchecks both the exact(model, provider)row and a wildcard(model, '')row.- Backfill on load:
register()seedsknown_free_modelswithprovider=''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-leveldictin__init__.py): mapssession_id → (model, cost). Populated bypost_api_requestwhen a previously-free model is seen at cost > 0. Consumed (popped) bypre_llm_call.pre_llm_callnow handles two injection paths: budget soft-alerts (anti-spam viabudget_alertstable) 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.pygained free→paid detection, queueing, injection, and backfill tests;test_db.pyandtest_pricing.pygained 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)indb.py: reverse-looks-up a stored<id>:freerow when a provider moves a model to paid under a different id (dropped:freesuffix or suffixed paid id at a token boundary). Wired intopost_api_requestasis_known_free_model(...) OR is_free_tier_transition(...).:freesuffix → $0 rule in_lookup_form: any…:freeid resolves to an explicit$0before the prefix scan (see Lookup priority chain). This both (a) stops a seeded model’s:freevariant from being mis-billed at its paid rate via prefix, and (b) records the:freeid 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-_subscriptionrequirement. 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.45rate.nvidia/nemotron-3-ultrapaid seed in_DEFAULT_PRICING(OpenRouter rate, NIM-direct pending). Catches the bare id (exact) and the…-550b-a55bform (prefix), makingcost > 0after the 2026-06-18 promo end — which fires the alert. The free…:freeforms are unaffected (handled by the suffix rule above).- 263 tests (was 253):
test_db.pygained 7is_free_tier_transitioncases;test_pricing.pygained the ultra paid-seed +:free-suffix → $0 (bare and OpenRouter-long-form),super:freeregression, 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 theprovider="moa"virtual-provider preset to its aggregator’s realprovider/modelviahermes_cli.moa_config.resolve_moa_preset. Defensive — swallows every error and falls back to the raw hook values.post_api_requestnow detects MoA calls and records them under the aggregator’s real provider/model (fixing theprovider_assumedmisfire) 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_llmfires no hooks) — documented as a Known Limitation; MoA cost is a lower bound and is flagged as such in/statsand 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 statuseserror/interrupted(source-verified against__init__.pysession end — not the subagent-onlyfailed/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__.pysession-end logic, whoseon_session_endkwargs are sourced fromagent/conversation_loop.py.
hermes-telemetry is an independent community plugin for Hermes Agent. It is not affiliated with or endorsed by Nous Research.