Summary
Cost estimates go sharply negative for routed / variable-price model slugs served through OpenRouter, because OpenRouter publishes those slugs with a negative pricing sentinel and Hermes prices the sentinel at face value.
Observed on the OpenRouter Jev router:
model.default: typesafe/jev-router
model.provider: openrouter
One ordinary turn (2 API calls, 78,487 input tokens, 64 output tokens) was recorded as:
estimated_cost_usd: -78551.0
cost_status: estimated
cost_source: provider_models_api
The same negative figure lands in session_model_usage.estimated_cost_usd, so /insights, /usage, any cost-based reporting, and any automation that gates on spend are all corrupted by it.
Root cause
agent/usage_pricing.py::_pricing_entry_from_metadata reads the OpenRouter catalog pricing and multiplies the per-token number by one million:
value = _to_decimal(raw)
return None if value is None else value * _ONE_MILLION
OpenRouter does not publish a fixed price for router slugs. GET /api/v1/models returns:
typesafe/jev-router pricing: {"prompt": "-1", "completion": "-1"}
openrouter/auto pricing: {"prompt": "-1", "completion": "-1"}
openrouter/pareto-code pricing: {"prompt": "-1", "completion": "-1"}
-1 means "the price is set by whichever model actually serves the request". Multiplied by 1e6 the entry becomes -1,000,000 per million tokens, hence the negative estimate. Any -1-priced catalog entry hits this, not just jev-router.
Suggested fix
Treat a negative catalog price as "unpriced" and let the cost fall back to unknown:
prompt = per_million("prompt")
completion = per_million("completion")
request = _to_decimal(pricing.get("request"))
+ # Routed / variable-price slugs (OpenRouter's ``typesafe/jev-router``, ``openrouter/auto``,
+ # ``openrouter/pareto-code``) are listed with a NEGATIVE sentinel instead of a number: the
+ # price is set by whichever model actually served the request. Pricing them from the sentinel
+ # produces a large negative estimate, which then poisons /insights, /usage and any
+ # cost-based reporting. Treat such a route as unpriced so cost falls back to unknown.
+ if any(value is not None and value < 0 for value in (prompt, completion, request)):
+ return None
if prompt is None and completion is None and request is None:
return None
Verified locally: with the guard, the same session flips from estimated_cost_usd: -78551.0 / cost_status: estimated to estimated_cost_usd: 0.0 / cost_status: unknown. The pricing test selection (pytest -k pricing) passes: 137 passed, 1 skipped.
Related improvement
For these routes OpenRouter returns the real per-call cost in the response body (usage.cost), so an actual-cost source exists without a catalog lookup. Reading it when the catalog price is missing or negative would give accurate numbers instead of unknown for router slugs.
Environment
- Hermes Agent v0.21.6+131.g38880bd (git install, install dir
~/.hermes/hermes-agent)
- Provider: OpenRouter (
provider: openrouter, api_mode: chat_completions)
- Router metadata confirmed the request was served by an upstream model (
deepseek/deepseek-v4.1-flash and openai/gpt-6-luna in two runs) while the session row carried the negative estimate
Summary
Cost estimates go sharply negative for routed / variable-price model slugs served through OpenRouter, because OpenRouter publishes those slugs with a negative pricing sentinel and Hermes prices the sentinel at face value.
Observed on the OpenRouter Jev router:
One ordinary turn (2 API calls, 78,487 input tokens, 64 output tokens) was recorded as:
The same negative figure lands in
session_model_usage.estimated_cost_usd, so/insights,/usage, any cost-based reporting, and any automation that gates on spend are all corrupted by it.Root cause
agent/usage_pricing.py::_pricing_entry_from_metadatareads the OpenRouter catalog pricing and multiplies the per-token number by one million:OpenRouter does not publish a fixed price for router slugs.
GET /api/v1/modelsreturns:-1 means "the price is set by whichever model actually serves the request". Multiplied by 1e6 the entry becomes -1,000,000 per million tokens, hence the negative estimate. Any
-1-priced catalog entry hits this, not just jev-router.Suggested fix
Treat a negative catalog price as "unpriced" and let the cost fall back to unknown:
prompt = per_million("prompt") completion = per_million("completion") request = _to_decimal(pricing.get("request")) + # Routed / variable-price slugs (OpenRouter's ``typesafe/jev-router``, ``openrouter/auto``, + # ``openrouter/pareto-code``) are listed with a NEGATIVE sentinel instead of a number: the + # price is set by whichever model actually served the request. Pricing them from the sentinel + # produces a large negative estimate, which then poisons /insights, /usage and any + # cost-based reporting. Treat such a route as unpriced so cost falls back to unknown. + if any(value is not None and value < 0 for value in (prompt, completion, request)): + return None if prompt is None and completion is None and request is None: return NoneVerified locally: with the guard, the same session flips from
estimated_cost_usd: -78551.0/cost_status: estimatedtoestimated_cost_usd: 0.0/cost_status: unknown. The pricing test selection (pytest -k pricing) passes: 137 passed, 1 skipped.Related improvement
For these routes OpenRouter returns the real per-call cost in the response body (
usage.cost), so an actual-cost source exists without a catalog lookup. Reading it when the catalog price is missing or negative would give accurate numbers instead ofunknownfor router slugs.Environment
~/.hermes/hermes-agent)provider: openrouter,api_mode: chat_completions)deepseek/deepseek-v4.1-flashandopenai/gpt-6-lunain two runs) while the session row carried the negative estimate