Monitor & track LLM API costs from Hermes state.db — queries, wiki pages, cron auto-updates, and cost dashboards.
Track LLM API costs using Hermes' built-in state.db telemetry. Hermes already records
per-session, per-model token usage and estimated costs in real time — this skill shows
how to surface that data into wiki pages, cron auto-updates, and cost dashboards.
Use this skill when the user:
state.db internals related to usage/costsession_model_usage TableHermes writes to state.db after every API call. The session_model_usage table has:
| Column | Description |
|---|---|
session_id |
Links to sessions table |
model |
Full model ID (e.g. deepseek/deepseek-v4-pro) |
billing_provider |
openrouter, anthropic, etc. |
api_call_count |
Number of API calls for this session+model pair |
input_tokens |
Prompt tokens sent |
output_tokens |
Completion tokens received |
reasoning_tokens |
Thinking/reasoning tokens (o1, deepseek-r1, claude thinking) |
cache_read_tokens |
Prompt caching hits (tokens served from cache — already paid for) |
cache_write_tokens |
Tokens written to cache |
estimated_cost_usd |
Cost estimate from provider's pricing API |
actual_cost_usd |
Actual cost if returned by API (rare — OpenRouter returns $0) |
cost_status |
estimated, actual, or null |
first_seen / last_seen |
Unix timestamps for the session+model activity window |
on first write. But they do reduce total cost vs. re-sending the full prompt each time.
A high cache-read ratio means prompt caching is working well.
Query state.db directly and write results to a wiki page:
import sqlite3, os
from datetime import datetime, timezone
HERMES_HOME = os.environ.get("HERMES_HOME", os.path.expanduser("~/.hermes"))
WIKI = os.environ.get("WIKI_PATH", os.path.expanduser("~/wiki"))
DB = os.path.join(HERMES_HOME, "state.db")
db = sqlite3.connect(DB)
db.row_factory = sqlite3.Row
All-time totals
totals = dict(db.execute("""
SELECT COUNT(DISTINCT session_id) as sessions,
SUM(api_call_count) as calls,
SUM(input_tokens) as input_tokens,
SUM(output_tokens) as output_tokens,
SUM(reasoning_tokens) as reasoning_tokens,
SUM(estimated_cost_usd) as est_cost
FROM session_model_usage
""").fetchone())
Per-model breakdown
by_model = db.execute("""
SELECT model, COUNT(DISTINCT session_id) as sessions,
SUM(api_call_count) as calls,
SUM(input_tokens) as input_tokens,
SUM(output_tokens) as output_tokens,
SUM(reasoning_tokens) as reasoning_tokens,
SUM(estimated_cost_usd) as est_cost
FROM session_model_usage GROUP BY model ORDER BY est_cost DESC
""").fetchall()
Daily breakdown
daily = db.execute("""
SELECT DATE(datetime(first_seen, 'unixepoch')) as date,
COUNT(DISTINCT session_id) as sessions,
SUM(api_call_count) as calls,
SUM(estimated_cost_usd) as est_cost
FROM session_model_usage GROUP BY date ORDER BY date DESC LIMIT 30
""").fetchall()
db.close()
Then build a markdown page with tables for each section.
Use a no_agent cron with a Python script that regenerates the wiki page each tick.
Zero LLM tokens — the script IS the job.
Step 1: Write the script (seescripts/openrouter-cost-update.py for a production example)
The script should:
1. Query state.db for all the data sections
2. Format as markdown with YAML frontmatter
3. Write to $WIKI_PATH/wealth/expense/openrouter-costs.md
4. Print a single-line confirmation to stdout
Step 2: Create the cron jobcronjob(action='create',
name='OpenRouter cost tracker',
schedule='0 6 *', # daily at 6am UTC
no_agent=True, # pure Python, zero tokens
script='scripts/openrouter-cost-update.py',
deliver='local') # stdout only; wiki page is the real deliverable
Why no_agent: The script does pure data → markdown transformation. No reasoning
needed. Zero token cost. The cron scheduler runs the script, captures stdout, and
delivers it. Empty stdout = silent run.
Enable inline cost display in the CLI/TUI:
hermes config set display.show_cost true
This shows estimated cost after each turn. Restart required.
OpenRouter pricing (per million tokens) varies by model. Common models as of mid-2026:
| Model | Input/M | Output/M | Notes |
|---|---|---|---|
| deepseek-v4-pro | $0.40 | $1.00 | Includes reasoning |
| deepseek-v4-flash | $0.05 | $0.21 | Fast fallback |
Cache reads reduce effective cost — when the system prompt and tool definitions
are cached (which Hermes does aggressively), input costs drop to cache-read rates
(~10% of full price). A healthy session should show 80-90% cache hit rate.
The tracking page should include (see references/cost-page-template.md for full example):
type: summary, tags: [hermes, ai, tracking, finance]state.db path: Use os.environ.get("HERMES_HOME", os.path.expanduser("~/.hermes")) — never hardcode ~/.hermes/state.db. The Hermes home can be overridden.WIKI_PATH env var: If set, the wiki is there; otherwise defaults to ~/wiki. The cron script must respect this.actual_cost_usd is usually $0: Most providers don't return actual cost in API responses. estimated_cost_usd is the reliable field.deliver: local with no_agent: The script's stdout becomes the delivery message. Keep it to one line — the wiki page is the real output. Empty stdout = silent delivery (nothing sent to user).~/.hermes: Use HERMES_HOME env var — cron jobs may run in different environments.LIMIT clauses to queries.cache_read_tokens column tracks how many of the input_tokens were served from cache.session_model_usage.session_id references sessions.id (not sessions.session_id). Always use smu.session_id = s.id when joining.sqlite3.Row, access by name (r['est_cost']), not by index — the column order in SELECT with aggregates is easy to miscount.tracking/ and wealth/expense/), cron scripts writing to different paths create silent data drift. When consolidating, use md5sum + wc -l to pick the most complete version, copy it to the canonical location, then grep -rn "old/path" ~/./scripts/ to update any cron scripts referencing the old path. Always update agents.md after structural changes.A focused weekly summary delivered before the user's retro. Separate from the
daily full-page regenerator — this one targets Slack delivery and includes
week-over-week comparison + threshold alerts.
Script:scripts/weekly-cost-audit.py
The script should:
1. Query state.db for this week vs last week (7-day windows)
2. Compute per-model breakdown, daily trend with visual bars
3. Flag expensive sessions (>$0.30)
4. Detect spikes: week total >$5 OR avg per-session >$0.50
5. Print a concise markdown summary to stdout (no wiki page — Slack is the output)
cronjob(action='create',
name='Weekly cost audit',
schedule='0 9 0', # Sunday 9am, 30min before retro
no_agent=True,
script='scripts/weekly-cost-audit.py',
deliver='origin') # delivers to Slack where retro fires
Why separate from Pattern 2: The daily tracker writes a full wiki page
(dense, for reference). The weekly audit is a short Slack message with trend
analysis and spike alerts — designed for quick scanning at retro time.
Spike thresholds (adjust per user):references/cost-page-template.md — Full markdown template for the auto-generated cost tracking page, including frontmatter, table structures, and the number formatting helper.scripts/weekly-cost-audit.py — Weekly cost audit script (no_agent cron). Queries past 7 days, compares week-over-week, detects spikes, delivers concise Slack summary. Used by the "Weekly cost audit" cron job.$HERMES_HOME/scripts/openrouter-cost-update.py — Production cron script that queries state.db and regenerates wiki/wealth/expense/openrouter-costs.md. Designed for cronjob(no_agent=True). Respects WIKI_PATH and HERMES_HOME env vars.